文件
fenleixinxi/application/api/controller/Auth.php
linda a09a0a10be feat(api): 新增小程序用户端与后台管理端 v1 业务接口(62 端点)
一、基础设施
- route.php:58 条路由覆盖 /api/v1/**;:id 用 \d+ 约束、长路径优先
- api/controller/V1.php:响应适配为 {code,message,data}(FastAdmin 原生 {code,msg,time,data} 不兼容);
  Authorization Bearer 鉴权;统一分页;业务公共 userInfo/ext/flow/notify/adminLog
- admin/command/FcInstall.php:19 张 fa_fc_* 表 + 种子数据,幂等可重跑

二、控制器 17 个
- 用户端 Auth/Home/Post/Member/Distribution/Message/Setting
- 后台端 AdminDashboard/AdminMerchant/AdminMember/AdminPost/AdminComment/
  AdminDist/AdminOrder/AdminFinance/AdminRbac/AdminSetting

三、契约一致性
- 字段与小程序 common/mock/data.js 逐字段对齐,Mock 与真实接口可对拍
- 实现 BR-01~BR-12,错误文案与前端 mock 完全一致
- 详情联系方式强制脱敏,付费/会员方可解锁

四、验证
- tools/verify-api.mjs:63 端点契约 + 12 条业务规则 + 8 步双端闭环
- docs/05-后端实现规范.md
2026-10-15 14:36:26 +08:00

170 行
5.9 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\api\controller;
use think\Db;
/**
* 用户端 · 认证与登录(Module-AUTH)
* 路由:
* POST /api/v1/auth/wx-login A1 微信授权登录
* POST /api/v1/auth/bind-phone A2 手机号绑定
* POST /api/v1/auth/switch-type A3 个人/商家身份切换
* GET /api/v1/auth/profile A4 个人资料
*/
class Auth extends V1
{
/**
* A1 微信一键授权登录
* 入参 { code } → 出参 { token, refreshToken, userInfo }
* 规则:code 必填;首次登录自动建档并生成邀请码
*/
public function wxLogin()
{
$code = trim((string)$this->input('code', ''));
if (!$code) {
$this->fail('缺少微信授权 code');
}
$openid = $this->resolveOpenid($code);
$ext = Db::name('fc_user_ext')->where('openid', $openid)->find();
$user = $ext ? Db::name('user')->where('id', $ext['user_id'])->find() : null;
if (!$user) {
// 首次登录:自动建号
$uid = Db::name('user')->insertGetId([
'group_id' => 1,
'username' => 'wx' . substr(md5($openid), 0, 10),
'nickname' => '同城用户' . mt_rand(1000, 9999),
'password' => md5(mt_rand()),
'salt' => 'wx',
'email' => substr(md5($openid), 0, 12) . '@wx.local',
'mobile' => '',
'avatar' => '',
'level' => 1,
'money' => 0,
'score' => 0,
'status' => 'normal',
'jointime' => time(),
'joinip' => $this->request->ip(),
'createtime' => time(),
'updatetime' => time(),
]);
Db::name('fc_user_ext')->insert([
'user_id' => $uid, 'openid' => $openid, 'user_type' => 1, 'realname_status' => 0,
'is_member' => 0, 'member_level' => '', 'member_expiretime' => null,
'invite_code' => strtoupper(substr(md5('fc' . $uid), 0, 6)), 'inviter_id' => 0,
'free_view_date' => date('Y-m-d'), 'free_view_used' => 0,
'createtime' => time(), 'updatetime' => time(),
]);
$user = Db::name('user')->where('id', $uid)->find();
}
if ($user['status'] != 'normal') {
$this->fail('账号已被禁用,请联系客服');
}
$token = $this->issueToken($user['id']);
$this->ok([
'token' => $token,
'refreshToken' => md5($token . 'refresh'),
'userInfo' => $this->userInfo($user),
]);
}
/**
* A2 手机号授权绑定
* 入参 { phone } → 出参 { phone }
* 规则 BR-02:必须匹配 ^1\d{10}$
*/
public function bindPhone()
{
$user = $this->auth();
$phone = trim((string)$this->input('phone', ''));
if (!preg_match('/^1\d{10}$/', $phone)) {
$this->fail('手机号格式不正确');
}
Db::name('user')->where('id', $user['id'])->update([
'mobile' => $phone, 'updatetime' => time(),
]);
$this->ok(['phone' => $phone]);
}
/**
* A3 用户类型切换
* 入参 { userType: 1|2, license? } → 出参 { userType, needAudit }
* 规则 BR-01:切换商家必须提交营业执照,提交后进入待审核
*/
public function switchType()
{
$user = $this->auth();
$userType = (int)$this->input('userType', 0);
if (!in_array($userType, [1, 2], true)) {
$this->fail('用户类型不合法');
}
if ($userType === 2) {
$license = trim((string)$this->input('license', ''));
if (!$license) {
$this->fail('商家用户需上传营业执照完成资质认证');
}
$name = trim((string)$this->input('merchantName', ''));
$exists = Db::name('fc_merchant')->where('user_id', $user['id'])->find();
$data = [
'user_id' => $user['id'],
'name' => $name !== '' ? $name : (string)$user['nickname'],
'license' => $license,
'audit_status' => 0,
'shop_status' => 'pending',
'updatetime' => time(),
];
if ($exists) {
Db::name('fc_merchant')->where('id', $exists['id'])->update($data);
} else {
$data['createtime'] = time();
Db::name('fc_merchant')->insert($data);
}
}
Db::name('fc_user_ext')->where('user_id', $user['id'])->update([
'user_type' => $userType, 'updatetime' => time(),
]);
$this->ok(['userType' => $userType, 'needAudit' => $userType === 2]);
}
/**
* A4 个人资料
* 返回 userInfo(含会员状态、余额)
*/
public function profile()
{
$user = $this->auth();
$this->ok($this->userInfo($user));
}
/**
* code -> openid
* 已配置微信参数时走 jscode2session;未配置(本地/联调)时用确定性派生,便于前后端联调。
*/
protected function resolveOpenid($code)
{
$appid = (string)$this->cfg('wx.appid', '');
$secret = (string)$this->cfg('wx.secret', '');
if ($appid && $secret) {
$url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $appid
. '&secret=' . $secret . '&js_code=' . urlencode($code) . '&grant_type=authorization_code';
$res = @json_decode(@file_get_contents($url), true);
if (!empty($res['openid'])) {
return $res['openid'];
}
}
// 未配置微信参数:若 code 本身即为 openid 标识则直接采用,否则派生
if (strpos($code, 'openid_') === 0) {
return $code;
}
return 'openid_' . md5($code);
}
}