Appearance
微信授权开发指南
概述
DSPlatform 实现了完整的微信授权体系,支持微信小程序、微信公众号等多种场景的授权登录、绑定和解绑功能。系统通过统一的授权接口,实现了多端用户身份的统一管理。
技术架构
核心组件
- WechatAuthService: 微信授权服务类,处理所有授权相关业务逻辑
- DeshangOfficialEasyService: 微信公众号服务封装
- DeshangMiniEasyService: 微信小程序服务封装
- UserIdentityDao: 用户身份数据访问层,管理微信绑定关系
- LoginService: 登录服务,处理登录后的业务逻辑
目录结构
app/api/
├── controller/
│ └── wechat/
│ ├── WechatAuth.php # 微信授权控制器
│ └── validate/
│ └── WechatAuthValidate.php # 参数验证器
├── service/
│ └── wechat/
│ └── WechatAuthService.php # 微信授权服务
└── route/
└── wechat.php # 微信路由配置授权场景
系统支持以下微信授权场景:
1. 微信小程序 (wechat_mini)
- 使用场景: 小程序内登录、绑定、支付
- 获取方式: 通过
uni.login()获取 code - 特点: 无需用户授权,直接获取 openid
2. 微信公众号 (wechat_official)
- 使用场景: 公众号内网页登录、绑定
- 获取方式: 通过 OAuth2.0 授权页面获取 code
- 授权范围:
snsapi_base: 静默授权,仅获取 openidsnsapi_userinfo: 需要用户确认,获取用户信息
授权模式
1. 登录模式 (login)
用途: 通过微信授权实现用户登录
流程:
- 前端获取微信 code
- 调用授权接口,传入
mode: 'login' - 后端通过 unionid 查找用户
- 如果用户存在,自动登录
- 如果用户不存在,自动注册并登录
特点:
- 必须通过 unionid 关联用户
- 支持自动注册
- 登录模式下 merchant_id 必须为 0(平台授权)
2. 绑定模式 (bind)
用途: 将微信账号绑定到已登录的用户
流程:
- 用户必须先登录
- 前端获取微信 code
- 调用授权接口,传入
mode: 'bind' - 后端检查该微信是否已被其他用户绑定
- 如果未绑定,创建绑定关系
特点:
- 需要用户已登录
- 通过 unionid 防止重复绑定
- 绑定前会删除用户原有的绑定关系
3. 获取 OpenID 模式 (getOpenid)
用途: 仅获取 openid,用于支付等场景
流程:
- 用户必须先登录
- 前端获取微信 code
- 调用授权接口,传入
mode: 'getOpenid' - 后端仅更新 openid,不更新 unionid
特点:
- 需要用户已登录
- 不更新 unionid,避免影响登录关联
- 主要用于支付场景
数据库设计
user_identity 表
用户身份关联表,存储用户与微信的绑定关系:
sql
CREATE TABLE `user_identity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`merchant_id` int(11) NOT NULL DEFAULT '0' COMMENT '商户ID',
`wx_oauth_openid` varchar(32) DEFAULT NULL COMMENT '微信网页授权openid',
`wx_mini_openid` varchar(32) DEFAULT NULL COMMENT '小程序openid',
`wx_app_openid` varchar(32) DEFAULT NULL COMMENT 'APP openid',
`wx_unionid` varchar(32) DEFAULT NULL COMMENT '微信unionid,用于账户绑定唯一标识',
`create_at` int(11) DEFAULT NULL,
`update_at` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `udx_wx_unionid` (`wx_unionid`),
KEY `idx_user_id` (`user_id`),
KEY `idx_merchant_id` (`merchant_id`),
KEY `idx_wx_oauth_openid` (`wx_oauth_openid`),
KEY `idx_wx_mini_openid` (`wx_mini_openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户身份关联表';字段说明
- wx_unionid: 微信 unionid,同一微信开放平台账号下唯一,用于关联同一用户的不同端
- wx_oauth_openid: 公众号 openid,用于公众号相关功能
- wx_mini_openid: 小程序 openid,用于小程序相关功能
- wx_app_openid: APP openid,用于 APP 相关功能
- merchant_id: 商户ID,0 表示平台授权
API 接口
1. 微信授权接口
接口地址: POST /api/wechat/auth
请求参数:
json
{
"code": "微信授权码",
"scene": "wechat_mini|wechat_official|app",
"mode": "login|bind|getOpenid",
"merchant_id": 0
}参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 微信授权码 |
| scene | string | 是 | 授权场景:wechat_mini/wechat_official/app |
| mode | string | 是 | 授权模式:login/bind/getOpenid |
| merchant_id | integer | 是 | 商户ID,登录和绑定时必须为0 |
响应示例:
json
{
"code": 10000,
"msg": "授权成功",
"data": {
"access_token": "xxx",
"access_expires_time": 1234567890,
"refresh_token": "xxx",
"refresh_expires_time": 1234567890,
"userinfo": {
"id": 1,
"username": "user123",
"nickname": "用户昵称"
}
}
}2. 检测微信绑定状态
接口地址: GET /api/wechat/check-auth
请求参数:
merchant_id: 商户ID
scene: 授权场景响应示例:
json
{
"code": 10000,
"msg": "操作成功",
"data": true // true表示已绑定,false表示未绑定
}3. 获取公众号授权URL
接口地址: POST /api/wechat/official-auth-url
请求参数:
json
{
"merchant_id": 0,
"scene": "wechat_official",
"mode": "login|bind|getOpenid",
"redirect_url": "授权成功后的回调地址"
}响应示例:
json
{
"code": 10000,
"msg": "操作成功",
"data": "https://open.weixin.qq.com/connect/oauth2/authorize?..."
}4. 获取 JS SDK 配置
接口地址: POST /api/wechat/js-sdk-config
请求参数:
json
{
"url": "当前页面URL",
"merchant_id": 0
}响应示例:
json
{
"code": 10000,
"msg": "操作成功",
"data": {
"appId": "wx1234567890",
"timestamp": 1234567890,
"nonceStr": "random_string",
"signature": "signature_string"
}
}服务层实现
WechatAuthService 核心方法
1. 获取微信授权信息
php
/**
* 获取微信授权信息
* @param array $data 授权参数
* @return array
*/
public function getWechatAuthInfo($data)
{
// 根据场景获取微信用户信息
if ($data['scene'] == 'wechat_official') {
$scope = $data['mode'] == 'getOpenid' ? 'snsapi_base' : 'snsapi_userinfo';
$wechatInfo = (new DeshangOfficialEasyService())
->init($data['merchant_id'])
->getWechatInfoByCode($data['code'], $scope);
} else if ($data['scene'] == 'wechat_mini') {
$wechatInfo = (new DeshangMiniEasyService())
->init($data['merchant_id'])
->getWechatInfoByCode($data['code']);
}
// 根据模式处理授权信息
if ($data['mode'] == 'login') {
return $this->handleLogin($wechatInfo, $data);
} else if ($data['mode'] == 'bind') {
return $this->handleBind($wechatInfo, $data);
} else if ($data['mode'] == 'getOpenid') {
return $this->handleGetOpenid($wechatInfo, $data);
}
}2. 处理登录
php
/**
* 处理登录微信授权信息
* @param array $wechatInfo 微信用户信息
* @param array $data 授权参数
* @return array
*/
private function handleLogin(array $wechatInfo, array $data)
{
// 验证 merchant_id
if ($data['merchant_id'] != 0) {
throw new CommonException('merchant_id 错误');
}
// 验证 unionid
if (empty($wechatInfo['unionid'])) {
throw new CommonException('用户信息unionid不存在');
}
// 通过 unionid 查找用户
$user_identity = (new UserIdentityDao())->getUserIdentityInfo([
['wx_unionid', '=', $wechatInfo['unionid']],
['merchant_id', '=', $data['merchant_id']]
]);
if (!empty($user_identity)) {
// 用户已存在,自动登录
$user_id = $user_identity['user_id'];
} else {
// 用户不存在,自动注册
$register_data = [
'username' => $this->generateRandomUsername(),
'password' => generateRandomString(6),
];
$user_id = (new DeshangUserService())->createUser($register_data);
}
// 更新用户绑定信息
$user_identity_data = [
'user_id' => $user_id,
'merchant_id' => $data['merchant_id'],
'wx_unionid' => $wechatInfo['unionid'],
];
// 根据场景设置对应的 openid
switch ($data['scene']) {
case 'wechat_official':
$user_identity_data['wx_oauth_openid'] = $wechatInfo['openid'];
break;
case 'wechat_mini':
$user_identity_data['wx_mini_openid'] = $wechatInfo['openid'];
break;
case 'wechat_app':
$user_identity_data['wx_app_openid'] = $wechatInfo['openid'];
break;
}
// 保存或更新绑定信息
if (empty($user_identity)) {
(new UserIdentityDao())->createUserIdentity($user_identity_data);
} else {
(new UserIdentityDao())->updateUserIdentity([
['user_id', '=', $user_id],
['merchant_id', '=', $data['merchant_id']],
['wx_unionid', '=', $wechatInfo['unionid']]
], $user_identity_data);
}
// 执行登录
$user_info = (new UserDao())->getUserInfo(['id' => $user_id]);
return (new LoginService())->loginAfter($user_info);
}3. 处理绑定
php
/**
* 处理绑定微信授权信息
* @param array $wechatInfo 微信用户信息
* @param array $data 授权参数
* @return array
*/
private function handleBind(array $wechatInfo, array $data)
{
// 验证用户已登录
if (!$this->user_id) {
throw new AuthException('请先登录');
}
// 验证 merchant_id
if ($data['merchant_id'] != 0) {
throw new CommonException('merchant_id 错误');
}
// 验证 unionid
if (empty($wechatInfo['unionid'])) {
throw new CommonException('用户信息unionid不存在');
}
// 检查该微信是否已被其他用户绑定
$user_identity = (new UserIdentityDao())->getUserIdentityInfo([
['wx_unionid', '=', $wechatInfo['unionid']],
['merchant_id', '=', $data['merchant_id']]
]);
if (!empty($user_identity)) {
throw new CommonException('此微信已经绑定了用户');
}
// 删除当前用户的所有绑定(避免冲突)
(new UserIdentityDao())->deleteUserIdentity([
['user_id', '=', $this->user_id],
['merchant_id', '=', $data['merchant_id']]
]);
// 创建新的绑定关系
$user_identity_data = [
'user_id' => $this->user_id,
'merchant_id' => $data['merchant_id'],
'wx_unionid' => $wechatInfo['unionid'],
];
// 根据场景设置对应的 openid
switch ($data['scene']) {
case 'wechat_official':
$user_identity_data['wx_oauth_openid'] = $wechatInfo['openid'];
break;
case 'wechat_mini':
$user_identity_data['wx_mini_openid'] = $wechatInfo['openid'];
break;
case 'wechat_app':
$user_identity_data['wx_app_openid'] = $wechatInfo['openid'];
break;
}
(new UserIdentityDao())->createUserIdentity($user_identity_data);
return $wechatInfo;
}4. 处理获取 OpenID
php
/**
* 处理获取 OpenID(用于支付等场景)
* @param array $wechatInfo 微信用户信息
* @param array $data 授权参数
* @return array
*/
private function handleGetOpenid(array $wechatInfo, array $data)
{
// 验证用户已登录
if (!$this->user_id) {
throw new AuthException('请先登录');
}
// 获取用户身份信息
$user_identity = (new UserIdentityDao())->getUserIdentityInfo([
['user_id', '=', $this->user_id],
['merchant_id', '=', $data['merchant_id']]
]);
// 仅更新 openid,不更新 unionid
$user_identity_data = [
'user_id' => $this->user_id,
'merchant_id' => $data['merchant_id'],
];
// 根据场景设置对应的 openid
switch ($data['scene']) {
case 'wechat_official':
$user_identity_data['wx_oauth_openid'] = $wechatInfo['openid'];
break;
case 'wechat_mini':
$user_identity_data['wx_mini_openid'] = $wechatInfo['openid'];
break;
case 'wechat_app':
$user_identity_data['wx_app_openid'] = $wechatInfo['openid'];
break;
}
// 保存或更新
if (empty($user_identity)) {
(new UserIdentityDao())->createUserIdentity($user_identity_data);
} else {
(new UserIdentityDao())->updateUserIdentity([
['user_id', '=', $this->user_id],
['merchant_id', '=', $data['merchant_id']]
], $user_identity_data);
}
return $wechatInfo;
}使用示例
小程序登录
前端代码 (uniapp):
typescript
// 小程序登录
uni.login({
provider: 'weixin',
success: async (loginRes) => {
const res = await wechatAuth({
code: loginRes.code,
scene: 'wechat_mini',
mode: 'login',
merchant_id: 0
});
if (res.code === 10000) {
// 登录成功,保存用户信息
const userStore = useUserStore();
userStore.loginAfter(res.data);
}
}
});后端调用:
php
// 控制器
public function getWechatAuthInfo()
{
$data = [
'merchant_id' => input('param.merchant_id', 0),
'code' => input('param.code', ''),
'scene' => input('param.scene', ''),
'mode' => input('param.mode', ''),
];
$this->validate($data, 'app\api\controller\wechat\validate\WechatAuthValidate.getWechatAuthInfo');
$result = (new WechatAuthService())->getWechatAuthInfo($data);
return ds_json_success('授权成功', $result);
}公众号登录
前端代码:
typescript
// 1. 获取授权URL
const res = await getOfficialAuthUrl({
merchant_id: 0,
scene: 'wechat_official',
mode: 'login',
redirect_url: window.location.href
});
// 2. 跳转到授权页面
if (res.code === 10000) {
window.location.href = res.data;
}
// 3. 授权回调后处理
onLoad((options) => {
if (options.code) {
wechatAuthUtil.handleOfficialCallback({
code: options.code,
mode: 'login',
merchant_id: 0
});
}
});绑定微信
前端代码:
typescript
// 用户已登录,绑定微信
uni.login({
provider: 'weixin',
success: async (loginRes) => {
const res = await wechatAuth({
code: loginRes.code,
scene: 'wechat_mini',
mode: 'bind',
merchant_id: 0
});
if (res.code === 10000) {
uni.showToast({
title: '绑定成功',
icon: 'success'
});
}
}
});获取 OpenID(用于支付)
前端代码:
typescript
// 用户已登录,获取 openid 用于支付
uni.login({
provider: 'weixin',
success: async (loginRes) => {
const res = await wechatAuth({
code: loginRes.code,
scene: 'wechat_mini',
mode: 'getOpenid',
merchant_id: 0
});
if (res.code === 10000) {
// openid 已保存,可以用于支付
proceedToPay();
}
}
});参数验证
WechatAuthValidate 验证器
php
class WechatAuthValidate extends BaseValidate
{
protected $rule = [
'merchant_id' => 'require|integer',
'scene' => 'require|in:wechat_official,wechat_mini,app',
'mode' => 'require|in:getOpenid,login,bind',
'redirect_url' => 'require|url',
'code' => 'string'
];
protected $message = [
'merchant_id.require' => '商户ID不能为空',
'merchant_id.integer' => '商户ID必须是整数',
'scene.require' => '场景不能为空',
'scene.in' => '场景必须是wechat_official,wechat_mini或app',
'mode.require' => '授权模式不能为空',
'mode.in' => '授权模式必须是getOpenid,login或bind',
'redirect_url.require' => '重定向URL不能为空',
'redirect_url.url' => '重定向URL格式不正确',
'code.string' => 'code必须是字符串'
];
protected $scene = [
'getWechatAuthInfo' => ['merchant_id', 'code', 'scene', 'mode'],
'checkWechatAuth' => ['merchant_id', 'scene'],
'getOfficialAuthUrl' => ['merchant_id', 'scene', 'mode', 'redirect_url'],
'getJsSdkConfig' => ['merchant_id', 'url']
];
}最佳实践
1. UnionID 关联策略
- 登录模式: 必须通过 unionid 关联用户,确保同一微信账号在不同端登录的是同一用户
- 绑定模式: 通过 unionid 防止重复绑定
- 获取 OpenID 模式: 不更新 unionid,避免影响登录关联
2. 自动注册策略
- 登录模式下,如果用户不存在,自动创建账号
- 用户名自动生成:
日期时间 + 随机字符串 - 密码自动生成:6位随机字符串
3. 绑定关系管理
- 绑定前先删除用户原有的绑定关系,避免冲突
- 通过 unionid 唯一索引防止重复绑定
- 支持解绑后重新绑定
4. 多端统一
- 同一微信账号在不同端(小程序、公众号)通过 unionid 关联
- 不同端的 openid 分别存储,互不影响
- 登录时通过 unionid 查找用户,实现多端统一
5. 安全性考虑
- 验证 code 的有效性
- 验证 unionid 的存在性
- 防止重复绑定
- 验证 merchant_id 的正确性
路由配置
php
// app/api/route/wechat.php
Route::group('wechat', function () {
// 微信授权
Route::post('auth', 'wechat.WechatAuth/getWechatAuthInfo');
// 检测微信授权
Route::get('check-auth', 'wechat.WechatAuth/checkWechatAuth');
// 获取微信授权URL
Route::post('official-auth-url', 'wechat.WechatAuth/getOfficialAuthUrl');
// 获取JS SDK配置
Route::post('js-sdk-config', 'wechat.WechatAuth/getJsSdkConfig');
})->middleware([
UserAuthorizeToken::class,
UserAuthorizeLog::class,
]);注意: 授权接口需要认证中间件,但登录模式时用户可能未登录,需要在中间件中特殊处理。
配置说明
微信配置
需要在管理后台配置微信相关参数:
- 小程序配置: AppID、AppSecret
- 公众号配置: AppID、AppSecret
- 开放平台配置: 需要绑定小程序和公众号,获取 unionid
环境要求
- 微信开放平台账号
- 已认证的小程序/公众号
- 小程序和公众号绑定到同一开放平台账号
常见问题
Q1: 为什么登录模式必须通过 unionid?
A: 因为同一微信账号在不同端(小程序、公众号)的 openid 是不同的,只有 unionid 是唯一的,可以确保多端登录的是同一用户。
Q2: 为什么获取 OpenID 模式不更新 unionid?
A: 因为可能存在用户通过普通方式登录,但支付时需要获取 openid 的情况。如果更新了 unionid,可能会影响原有的登录关联关系。
Q3: 如何解绑微信?
A: 删除 user_identity 表中对应的记录即可。注意需要同时删除 unionid 和对应的 openid。
最后更新:2024-01-20
维护者:DSPlatform技术团队(德尚网络)
获取帮助
如果您在使用过程中遇到问题,可以通过以下方式获取帮助:
- 官方网站:https://www.csdeshang.com
- 电话咨询:15364080101(微信同号)
- QQ咨询:858761000
- 邮箱咨询:858761000@qq.com
- 工作时间:工作日 9:00-18:00
- 微信咨询:扫码添加微信

版权所有 © 2014-至今 德尚网络