diff --git a/backend/.env.example b/backend/.env.example
index 172314e6..dc201f9e 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -50,6 +50,7 @@ DATABASE_URL=mysql+pymysql://root:password@localhost:3306/zkxw
# 微信支付配置示例
# 小程序支付使用 JSAPI(trade_type=JSAPI)
+# 同时复用于小程序微信登录(code2session / 手机号):WXPAY_MINI_APPID + WXPAY_SECRET
# WXPAY_MCHID=1900000109
# WXPAY_MINI_APPID=wx1234567890abcdef
# WXPAY_SECRET=your-miniapp-secret
diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py
index 72153387..6a379b6f 100644
--- a/backend/app/api/__init__.py
+++ b/backend/app/api/__init__.py
@@ -34,6 +34,7 @@ from app.api.cleanroom_garment import router as cleanroom_garment_router
from app.api.market import router as market_router
from app.api.pre_transit_sample import router as pre_transit_sample_router
from app.api.oa_approval import router as oa_approval_router
+from app.api.mini_auth import router as mini_auth_router
__all__ = [
"public_router",
@@ -71,4 +72,5 @@ __all__ = [
"market_router",
"pre_transit_sample_router",
"oa_approval_router",
+ "mini_auth_router",
]
diff --git a/backend/app/api/mini_auth.py b/backend/app/api/mini_auth.py
new file mode 100644
index 00000000..b81053c2
--- /dev/null
+++ b/backend/app/api/mini_auth.py
@@ -0,0 +1,130 @@
+"""小程序鉴权:微信登录 / 绑定 openid。"""
+from __future__ import annotations
+
+from datetime import datetime
+
+from fastapi import APIRouter, Depends, HTTPException, Request
+from sqlalchemy.orm import Session
+
+from app.core.auth import get_current_user_by_token
+from app.core.database import get_db
+from app.core.response import error_response, success_response
+from app.crud.user import user_crud
+from app.services.issue_user_tokens import issue_tokens_for_user
+from app.services.wechat_mini_auth import (
+ WechatMiniAuthError,
+ code2session,
+ get_phone_number,
+)
+
+router = APIRouter(prefix="/mini/auth", tags=["小程序鉴权"])
+
+
+async def _resolve_user_id(request: Request, db: Session) -> str:
+ authorization = request.headers.get("Authorization", "")
+ if not authorization.startswith("Bearer "):
+ raise HTTPException(status_code=401, detail="未授权")
+ token = authorization.replace("Bearer ", "")
+ current = await get_current_user_by_token(token=token, db=db)
+ user_id = current.get("user_id") or current.get("sub")
+ if not user_id:
+ raise HTTPException(status_code=401, detail="未授权")
+ return user_id
+
+
+@router.post("/wechat-login", response_model=dict)
+async def wechat_login(body: dict, db: Session = Depends(get_db)):
+ """微信授权登录:优先 openid;未绑定则用手机号匹配员工并绑定。"""
+ login_code = (body.get("loginCode") or "").strip()
+ phone_code = (body.get("phoneCode") or "").strip() or None
+
+ try:
+ sess = code2session(login_code)
+ except WechatMiniAuthError as exc:
+ return error_response(400, str(exc))
+
+ openid = sess["openid"]
+ user = user_crud.get_by_wx_openid(db, openid)
+ if user:
+ try:
+ data = await issue_tokens_for_user(db, user)
+ except ValueError as exc:
+ return error_response(403, str(exc))
+ return success_response(data)
+
+ if not phone_code:
+ return error_response(400, "请授权手机号登录,或使用账号密码登录")
+
+ try:
+ phone = get_phone_number(phone_code)
+ except WechatMiniAuthError as exc:
+ return error_response(400, str(exc))
+
+ users = user_crud.list_active_by_phone_no(db, phone)
+ if not users:
+ return error_response(
+ 401, "未找到与该手机号匹配的员工账号,请使用账号密码登录"
+ )
+ if len(users) > 1:
+ return error_response(400, "手机号对应多个账号,请使用账号密码登录")
+
+ user = users[0]
+ other = user_crud.get_by_wx_openid(db, openid)
+ if other and other.id != user.id:
+ return error_response(409, "该微信已绑定其他账号")
+ existing_oid = (getattr(user, "wx_openid", None) or "").strip()
+ if existing_oid and existing_oid != openid:
+ return error_response(409, "该账号已绑定其他微信,请联系管理员")
+
+ user.wx_openid = openid
+ user.update_time = datetime.now()
+ db.add(user)
+ db.commit()
+ db.refresh(user)
+
+ try:
+ data = await issue_tokens_for_user(db, user)
+ except ValueError as exc:
+ return error_response(403, str(exc))
+ return success_response(data)
+
+
+@router.post("/wechat-bind", response_model=dict)
+async def wechat_bind(
+ request: Request,
+ body: dict,
+ db: Session = Depends(get_db),
+):
+ """密码登录后绑定当前微信 openid(需 Bearer)。"""
+ try:
+ user_id = await _resolve_user_id(request, db)
+ except HTTPException:
+ return error_response(401, "未授权")
+
+ login_code = (body.get("loginCode") or "").strip()
+ try:
+ sess = code2session(login_code)
+ except WechatMiniAuthError as exc:
+ return error_response(400, str(exc))
+
+ openid = sess["openid"]
+ user = user_crud.get(db, user_id)
+ if not user or user.del_flag != 0:
+ return error_response(401, "用户不存在")
+
+ other = user_crud.get_by_wx_openid(db, openid)
+ if other and other.id != user.id:
+ return error_response(409, "该微信已绑定其他账号")
+
+ existing_oid = (getattr(user, "wx_openid", None) or "").strip()
+ if existing_oid == openid:
+ return success_response({"bound": True, "message": "已绑定"})
+
+ if existing_oid and existing_oid != openid:
+ return error_response(409, "该账号已绑定其他微信,请联系管理员")
+
+ user.wx_openid = openid
+ user.update_time = datetime.now()
+ db.add(user)
+ db.commit()
+ return success_response({"bound": True, "message": "绑定成功"})
diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index 7966299b..eedb0f97 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -134,6 +134,7 @@ class Settings(BaseSettings):
"/api/web/auth/login-rsa-public-key,"
"/api/web/auth/login,"
"/api/web/auth/refresh-token,"
+ "/api/mini/auth/wechat-login,"
"/api/web/admin/attachment/upload,"
"/api/web/admin/products/upload-image,"
"/api/web/admin/categories/upload-image,"
diff --git a/backend/app/crud/user.py b/backend/app/crud/user.py
index 991f3b29..751117cc 100644
--- a/backend/app/crud/user.py
+++ b/backend/app/crud/user.py
@@ -31,6 +31,30 @@ class _UserCRUDCompat(CRUDBase[SysUser]):
.first()
)
+ def get_by_wx_openid(self, db: Session, wx_openid: str) -> Optional[SysUser]:
+ oid = (wx_openid or "").strip()
+ if not oid:
+ return None
+ return (
+ db.query(SysUser)
+ .filter(SysUser.wx_openid == oid, SysUser.del_flag == 0)
+ .first()
+ )
+
+ def list_active_by_phone_no(self, db: Session, phone_no: str) -> List[SysUser]:
+ phone = (phone_no or "").strip()
+ if not phone:
+ return []
+ return (
+ db.query(SysUser)
+ .filter(
+ SysUser.phone_no == phone,
+ SysUser.del_flag == 0,
+ SysUser.status == "1",
+ )
+ .all()
+ )
+
user_crud = _UserCRUDCompat(SysUser)
diff --git a/backend/app/main.py b/backend/app/main.py
index 80db824e..7e9f0dc5 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -13,7 +13,7 @@ from app.core.database import SessionLocal
from app.core.api_request_log_middleware import ApiRequestLogMiddleware
from app.core.api_crypto_middleware import ApiCryptoMiddleware
from app.models import models # noqa: F401
-from app.api import public_router, menu_router, admin_router, workflow_router, basic_info_router, collection_protocol_router, leave_router, operations_router, ethnic_router, sample_reception_router, sample_production_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, qc_sms_notification_router, protocol_sample_collection_router, research_donation_sample_collection_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_router, clinical_payment_router, env_detect_router, cleanroom_garment_router, market_router, pre_transit_sample_router, oa_approval_router
+from app.api import public_router, menu_router, admin_router, workflow_router, basic_info_router, collection_protocol_router, leave_router, operations_router, ethnic_router, sample_reception_router, sample_production_reception_router, qc_router, research_donation_router, production_router, pilot_service_router, insurance_company_router, material_router, storage_release_review_router, sample_sms_notification_router, storage_entry_sms_notification_router, qc_sms_notification_router, protocol_sample_collection_router, research_donation_sample_collection_router, pilot_application_router, pilot_checkup_router, pilot_delivery_router, pilot_infusion_confirm_router, pilot_infusion_schedule_mgmt_router, outbound_release_review_router, clinical_payment_router, env_detect_router, cleanroom_garment_router, market_router, pre_transit_sample_router, oa_approval_router, mini_auth_router
@asynccontextmanager
@@ -133,6 +133,7 @@ app.include_router(cleanroom_garment_router, prefix=settings.API_V1_PREFIX)
app.include_router(market_router, prefix=settings.API_V1_PREFIX)
app.include_router(pre_transit_sample_router, prefix=settings.API_V1_PREFIX)
app.include_router(oa_approval_router, prefix=settings.API_V1_PREFIX)
+app.include_router(mini_auth_router, prefix=settings.API_V1_PREFIX)
def _mask_secret(value: str) -> str:
diff --git a/backend/app/models/models.py b/backend/app/models/models.py
index 62cac070..0b2c0545 100644
--- a/backend/app/models/models.py
+++ b/backend/app/models/models.py
@@ -31,6 +31,7 @@ class SysUser(Base):
nick_name = Column(String(30), nullable=False, comment="用户名")
email = Column(String(50), nullable=False, default="", comment="用户邮箱")
phone_no = Column(String(11), nullable=False, default="", comment="手机号码")
+ wx_openid = Column(String(64), nullable=True, comment="微信小程序 openid")
hire_date = Column(Date, nullable=True, comment="入职日期")
probation_months = Column(Integer, nullable=True, comment="试用期月数")
last_work_day = Column(Date, nullable=True, comment="最后工作日")
diff --git a/backend/app/services/issue_user_tokens.py b/backend/app/services/issue_user_tokens.py
new file mode 100644
index 00000000..1252c01c
--- /dev/null
+++ b/backend/app/services/issue_user_tokens.py
@@ -0,0 +1,102 @@
+"""为已通过校验的 SysUser 签发与 web 登录一致的 JWT 会话包。"""
+from __future__ import annotations
+
+from typing import Optional
+
+from sqlalchemy.orm import Session
+
+from app.core.auth import (
+ create_access_token,
+ create_refresh_token,
+ store_refresh_token,
+ store_session,
+)
+from app.core.config import settings
+from app.core.utils import generate_uuid
+from app.models.models import SysUser
+from app.services.web_auth_roles import (
+ get_user_role_names,
+ resolve_web_role_for_user,
+ user_has_active_roles,
+)
+
+
+def _avatar_url_field(user: SysUser) -> Optional[str]:
+ raw = getattr(user, "avatar", None)
+ if not raw:
+ return None
+ s = str(raw).strip()
+ return s if s else None
+
+
+def build_user_info_payload(user: SysUser, web_role: str, role_names: list = None) -> dict:
+ phone = (getattr(user, "phone_no", None) or "").strip() or None
+ return {
+ "id": user.id,
+ "username": user.user_no,
+ "realName": user.nick_name,
+ "phone": phone,
+ "email": (getattr(user, "email", None) or "").strip() or None,
+ "hireDate": user.hire_date.strftime("%Y-%m-%d") if getattr(user, "hire_date", None) else None,
+ "probationMonths": getattr(user, "probation_months", None),
+ "lastWorkDay": user.last_work_day.strftime("%Y-%m-%d")
+ if getattr(user, "last_work_day", None)
+ else None,
+ "sex": getattr(user, "sex", None) or "0",
+ "remark": (getattr(user, "remark", None) or "").strip() or None,
+ "role": web_role,
+ "roleNames": role_names or [],
+ "regionId": user.dept_id,
+ "regionName": None,
+ "avatarUrl": _avatar_url_field(user),
+ }
+
+
+def _jwt_user_payload(user: SysUser, web_role: str) -> dict:
+ return {
+ "sub": user.id,
+ "username": user.user_no,
+ "role": web_role,
+ "region_id": user.dept_id,
+ "real_name": user.nick_name,
+ }
+
+
+async def issue_tokens_for_user(db: Session, user: SysUser) -> dict:
+ """校验停用/角色后签发 token。失败抛 ValueError(中文 message)。"""
+ if user.status != "1":
+ raise ValueError("账号已被停用,请联系管理员")
+ if not user_has_active_roles(db, user.id):
+ raise ValueError("账号未分配角色,请联系管理员")
+
+ web_role = resolve_web_role_for_user(db, user.id)
+ role_names = get_user_role_names(db, user.id)
+
+ session_id = generate_uuid()
+ access_jti = generate_uuid()
+ refresh_jti = generate_uuid()
+
+ access_token = create_access_token(
+ data=_jwt_user_payload(user, web_role),
+ session_id=session_id,
+ token_jti=access_jti,
+ )
+ refresh_token = create_refresh_token(
+ data={
+ "sub": user.id,
+ "username": user.user_no,
+ "role": web_role,
+ },
+ session_id=session_id,
+ refresh_jti=refresh_jti,
+ )
+
+ await store_session(session_id=session_id, user_id=user.id)
+ await store_refresh_token(refresh_jti=refresh_jti, user_id=user.id)
+
+ return {
+ "token": access_token,
+ "refreshToken": refresh_token,
+ "expiresIn": settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60,
+ "userInfo": build_user_info_payload(user, web_role, role_names),
+ }
diff --git a/backend/app/services/wechat_mini_auth.py b/backend/app/services/wechat_mini_auth.py
new file mode 100644
index 00000000..39f55325
--- /dev/null
+++ b/backend/app/services/wechat_mini_auth.py
@@ -0,0 +1,122 @@
+"""微信小程序登录:code2session、手机号快速验证。"""
+from __future__ import annotations
+
+import logging
+import re
+from typing import Any, Dict, Optional
+
+import httpx
+
+from app.core.config import settings
+
+logger = logging.getLogger(__name__)
+
+_WX_CODE2SESSION = "https://api.weixin.qq.com/sns/jscode2session"
+_WX_TOKEN = "https://api.weixin.qq.com/cgi-bin/token"
+_WX_PHONE = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"
+
+
+class WechatMiniAuthError(Exception):
+ def __init__(self, message: str, *, wx_errcode: Optional[int] = None):
+ super().__init__(message)
+ self.wx_errcode = wx_errcode
+
+
+def normalize_cn_mobile(raw: Optional[str]) -> str:
+ if raw is None:
+ return ""
+ s = re.sub(r"\s+", "", str(raw).strip())
+ if s.startswith("+86"):
+ s = s[3:]
+ elif s.startswith("86") and len(s) == 13:
+ s = s[2:]
+ return s
+
+
+def _require_mini_credentials() -> tuple:
+ appid = (settings.WXPAY_MINI_APPID or "").strip()
+ secret = (settings.WXPAY_SECRET or "").strip()
+ if not appid or not secret:
+ raise WechatMiniAuthError("未配置小程序凭证(WXPAY_MINI_APPID / WXPAY_SECRET)")
+ return appid, secret
+
+
+def code2session(login_code: str) -> Dict[str, Any]:
+ code = (login_code or "").strip()
+ if not code:
+ raise WechatMiniAuthError("loginCode 不能为空")
+ appid, secret = _require_mini_credentials()
+ with httpx.Client(timeout=15.0) as client:
+ r = client.get(
+ _WX_CODE2SESSION,
+ params={
+ "appid": appid,
+ "secret": secret,
+ "js_code": code,
+ "grant_type": "authorization_code",
+ },
+ )
+ r.raise_for_status()
+ data = r.json()
+ if data.get("errcode"):
+ logger.warning("code2session failed: %s", data)
+ raise WechatMiniAuthError(
+ "微信登录凭证无效或已过期", wx_errcode=data.get("errcode")
+ )
+ openid = data.get("openid")
+ if not openid:
+ raise WechatMiniAuthError("微信未返回 openid")
+ return {
+ "openid": openid,
+ "session_key": data.get("session_key"),
+ "unionid": data.get("unionid"),
+ }
+
+
+def _get_access_token() -> str:
+ appid, secret = _require_mini_credentials()
+ with httpx.Client(timeout=15.0) as client:
+ r = client.get(
+ _WX_TOKEN,
+ params={
+ "grant_type": "client_credential",
+ "appid": appid,
+ "secret": secret,
+ },
+ )
+ r.raise_for_status()
+ data = r.json()
+ token = data.get("access_token")
+ if not token:
+ logger.warning("get access_token failed: %s", data)
+ raise WechatMiniAuthError(
+ "获取微信 access_token 失败", wx_errcode=data.get("errcode")
+ )
+ return token
+
+
+def get_phone_number(phone_code: str) -> str:
+ code = (phone_code or "").strip()
+ if not code:
+ raise WechatMiniAuthError("phoneCode 不能为空")
+ token = _get_access_token()
+ with httpx.Client(timeout=15.0) as client:
+ r = client.post(
+ f"{_WX_PHONE}?access_token={token}",
+ json={"code": code},
+ )
+ r.raise_for_status()
+ data = r.json()
+ if data.get("errcode") not in (0, None):
+ logger.warning("getuserphonenumber failed: %s", data)
+ raise WechatMiniAuthError(
+ "获取手机号失败,请改用账号密码登录",
+ wx_errcode=data.get("errcode"),
+ )
+ info = data.get("phone_info") or {}
+ phone = normalize_cn_mobile(
+ info.get("purePhoneNumber") or info.get("phoneNumber")
+ )
+ if not phone:
+ raise WechatMiniAuthError("微信未返回手机号")
+ return phone
diff --git a/backend/tests/test_wechat_mini_auth_phone.py b/backend/tests/test_wechat_mini_auth_phone.py
new file mode 100644
index 00000000..d831da0c
--- /dev/null
+++ b/backend/tests/test_wechat_mini_auth_phone.py
@@ -0,0 +1,15 @@
+"""手机号规范化单测(微信登录匹配 sys_user.phone_no)。"""
+from app.services.wechat_mini_auth import normalize_cn_mobile
+
+
+def test_normalize_strips_plus_86():
+ assert normalize_cn_mobile("+8613800138000") == "13800138000"
+
+
+def test_normalize_strips_spaces():
+ assert normalize_cn_mobile(" 138 0013 8000 ") == "13800138000"
+
+
+def test_normalize_empty():
+ assert normalize_cn_mobile("") == ""
+ assert normalize_cn_mobile(None) == ""
diff --git a/docs/sql/alter-sys-user-wx-openid.sql b/docs/sql/alter-sys-user-wx-openid.sql
new file mode 100644
index 00000000..976a87be
--- /dev/null
+++ b/docs/sql/alter-sys-user-wx-openid.sql
@@ -0,0 +1,8 @@
+-- 员工微信小程序 openid 绑定字段(微信授权登录)
+-- MySQL 5.7:唯一索引允许多个 NULL
+
+ALTER TABLE `sys_user`
+ ADD COLUMN `wx_openid` varchar(64) DEFAULT NULL COMMENT '微信小程序 openid' AFTER `phone_no`;
+
+ALTER TABLE `sys_user`
+ ADD UNIQUE INDEX `uk_sys_user_wx_openid` (`wx_openid`);
diff --git a/mobile-h5/manifest.json b/mobile-h5/manifest.json
index c01bfcf9..b6e59c76 100644
--- a/mobile-h5/manifest.json
+++ b/mobile-h5/manifest.json
@@ -50,7 +50,7 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
- "appid" : "",
+ "appid" : "wxa261ad85f2c99b26",
"setting" : {
"urlCheck" : false
},
diff --git a/mobile-h5/pages/login/index.vue b/mobile-h5/pages/login/index.vue
index 51af3a50..6ac7fdc5 100644
--- a/mobile-h5/pages/login/index.vue
+++ b/mobile-h5/pages/login/index.vue
@@ -74,7 +74,23 @@
或
-
+
+
微信授权登录
@@ -105,6 +121,7 @@
import {
isLoggedIn,
passwordLogin,
+ wechatLogin,
clearLegacyMockSessionIfNeeded
} from '../../utils/auth.js'
import { validateCredentials } from '../../utils/validate-credentials.js'
@@ -199,6 +216,50 @@
} finally {
this.loading = false
}
+ },
+ async onWechatGate() {
+ if (this.loading) return
+ await this.ensureAgree()
+ },
+ async onGetPhoneNumber(e) {
+ if (this.loading) return
+ if (!(await this.ensureAgree())) return
+ const detail = (e && e.detail) || {}
+ const errMsg = detail.errMsg || ''
+ if (errMsg && errMsg.indexOf('ok') === -1) {
+ uni.showToast({
+ title: '需要授权手机号,或请使用账号密码登录',
+ icon: 'none'
+ })
+ return
+ }
+ const phoneCode = detail.code
+ if (!phoneCode) {
+ uni.showToast({
+ title: '需要授权手机号,或请使用账号密码登录',
+ icon: 'none'
+ })
+ return
+ }
+ this.loading = true
+ try {
+ const loginRes = await new Promise((resolve, reject) => {
+ uni.login({ provider: 'weixin', success: resolve, fail: reject })
+ })
+ await wechatLogin({
+ loginCode: loginRes.code,
+ phoneCode
+ })
+ uni.showToast({ title: '登录成功', icon: 'success' })
+ setTimeout(() => this.goHome(), 400)
+ } catch (err) {
+ uni.showToast({
+ title: (err && err.message) || '登录失败',
+ icon: 'none'
+ })
+ } finally {
+ this.loading = false
+ }
}
}
}
diff --git a/mobile-h5/styles/components/login.scss b/mobile-h5/styles/components/login.scss
index 4d13a29e..5db96f9a 100644
--- a/mobile-h5/styles/components/login.scss
+++ b/mobile-h5/styles/components/login.scss
@@ -159,6 +159,15 @@
font-size: 28rpx;
font-weight: 500;
color: var(--text-primary);
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ line-height: normal;
+ box-sizing: border-box;
+}
+
+button.login-wechat::after {
+ border: none;
}
.login-wechat.is-disabled {
diff --git a/mobile-h5/utils/auth.js b/mobile-h5/utils/auth.js
index 502b4c55..b864116f 100644
--- a/mobile-h5/utils/auth.js
+++ b/mobile-h5/utils/auth.js
@@ -87,5 +87,49 @@ export async function passwordLogin({ username, password }) {
refreshToken: data.refreshToken,
userInfo: data.userInfo
})
+ // 密码登录成功后静默绑定微信(失败不影响登录)
+ bindWechatQuietly()
return data
}
+
+/**
+ * 微信授权登录(openid 优先;未绑定需 phoneCode)。
+ */
+export async function wechatLogin({ loginCode, phoneCode }) {
+ const payload = { loginCode }
+ if (phoneCode) {
+ payload.phoneCode = phoneCode
+ }
+ const data = await post('/mini/auth/wechat-login', payload)
+ if (!data || !data.token) {
+ throw new Error('登录失败')
+ }
+ saveSession({
+ token: data.token,
+ refreshToken: data.refreshToken,
+ userInfo: data.userInfo
+ })
+ return data
+}
+
+/**
+ * 已登录态下用 wx.login code 绑定 openid;失败静默。
+ */
+export async function bindWechatQuietly() {
+ try {
+ const loginRes = await new Promise((resolve, reject) => {
+ uni.login({ provider: 'weixin', success: resolve, fail: reject })
+ })
+ const loginCode = loginRes && loginRes.code
+ if (!loginCode) return
+ const token = getToken()
+ if (!token) return
+ await post(
+ '/mini/auth/wechat-bind',
+ { loginCode },
+ { Authorization: 'Bearer ' + token }
+ )
+ } catch (e) {
+ console.warn('wechat bind skipped', e && e.message)
+ }
+}