- 新增 invoice_title、showcase、wallet_log 模型及对应语言包 - 新增后台控制器、视图及搜索字段配置 - 修复服务类目删除保护逻辑 - 优化后台登录页面文案及样式 - 新增前端角色切换逻辑及页面拦截 - 新增 SVG 图标及样式变量 - 修复接口权限及下拉框默认值问题
456 行
19 KiB
PHP
456 行
19 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use fast\Random;
|
||
use think\Db;
|
||
use think\Validate;
|
||
|
||
/**
|
||
* 登录 / 角色 / 个人信息
|
||
*/
|
||
class Auth extends Base
|
||
{
|
||
protected $noNeedLogin = ['login', 'wxlogin', 'wxPhoneLogin'];
|
||
|
||
/**
|
||
* 手机号 + 验证码登录(兜底通道)
|
||
* 验证码来源由 .env 的 [sms] 段控制:mock=1 时使用 mock_code(联调用),
|
||
* 接入真实短信后置 mock=0 并在此处改为 Sms::check($mobile, $code, 'mobilelogin')
|
||
* @ApiMethod (POST)
|
||
*/
|
||
public function login()
|
||
{
|
||
$mobile = trim((string)$this->request->post('mobile', $this->request->post('account', '')));
|
||
$mobile = preg_replace('/[^\d]/', '', $mobile);
|
||
$password = (string)$this->request->post('password', '');
|
||
$code = trim((string)$this->request->post('code', ''));
|
||
|
||
// 1. 手机号 + 密码登录(直连真实数据库)
|
||
if ($password !== '' || ($mobile !== '' && $code === '')) {
|
||
if ($mobile === '') {
|
||
$this->error('请填写手机号');
|
||
}
|
||
if (!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
|
||
$this->error('手机号格式不正确');
|
||
}
|
||
if ($password === '') {
|
||
$this->error('请填写登录密码');
|
||
}
|
||
|
||
// 直接查询真实数据库 fa_user 表
|
||
$user = Db::name('user')->where('mobile', $mobile)->find();
|
||
|
||
if (!$user) {
|
||
// 若数据库中无此手机号:直连数据库插入注册新会员
|
||
$ret = $this->auth->register($mobile, $password, '', $mobile, []);
|
||
if (!$ret) {
|
||
$this->error($this->auth->getError() ?: '注册登录失败');
|
||
}
|
||
} else {
|
||
if ($user['status'] !== 'normal') {
|
||
$this->error('账号已被禁用');
|
||
}
|
||
if (empty($user['password'])) {
|
||
$this->error('该账号尚未设置登录密码,请使用验证码或微信登录后设置密码');
|
||
}
|
||
$encryptPassword = $this->auth->getEncryptPassword($password, $user['salt']);
|
||
if ($user['password'] !== $encryptPassword) {
|
||
$this->error('手机号或密码不正确');
|
||
}
|
||
$this->auth->direct($user['id']);
|
||
}
|
||
|
||
// 直查真实数据库返回用户完整信息
|
||
$this->success('登录成功', [
|
||
'token' => $this->auth->getToken(),
|
||
'expires' => 2592000,
|
||
'userinfo' => $this->profileData(),
|
||
]);
|
||
}
|
||
|
||
// 2. 手机号 + 验证码登录(向下兼容)
|
||
if (!$mobile || !$code) {
|
||
$this->error('请填写手机号和验证码');
|
||
}
|
||
if (!Validate::regex($mobile, "^1\d{10}$")) {
|
||
$this->error('手机号格式不正确');
|
||
}
|
||
|
||
$mockOn = (int)\think\Env::get('sms.mock', 1);
|
||
$mockCode = (string)\think\Env::get('sms.mock_code', '123456');
|
||
if (!($mockOn === 1 && $mockCode !== '' && (string)$code === $mockCode)) {
|
||
$this->error('短信通道尚未接入,请联系管理员开通', ['sms_ready' => 0], 2010);
|
||
}
|
||
|
||
$user = \app\common\model\User::getByMobile($mobile);
|
||
if ($user) {
|
||
if ($user['status'] !== 'normal') {
|
||
$this->error('账号已被禁用');
|
||
}
|
||
$this->auth->direct($user['id']);
|
||
} else {
|
||
$ret = $this->auth->register($mobile, Random::alnum(), '', $mobile, []);
|
||
if (!$ret) {
|
||
$this->error($this->auth->getError() ?: '注册失败');
|
||
}
|
||
}
|
||
|
||
$this->success('登录成功', [
|
||
'token' => $this->auth->getToken(),
|
||
'expires' => 2592000,
|
||
'userinfo' => $this->profileData(),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 我的信息(含角色与师傅状态)
|
||
*/
|
||
public function profile()
|
||
{
|
||
$this->success('', $this->profileData());
|
||
}
|
||
|
||
/**
|
||
* 组装个人信息
|
||
*/
|
||
protected function profileData()
|
||
{
|
||
$uid = $this->uid();
|
||
$user = Db::name('user')->field('id,nickname,mobile,email,avatar,money,score,user_type,contact_address,createtime')->where('id', $uid)->find();
|
||
$master = Db::name('master')->where('user_id', $uid)->find();
|
||
|
||
$couponCount = Db::name('user_coupon')->where('user_id', $uid)->where('status', 'unused')->count();
|
||
$unread = Db::name('message')->where('receive_type', 'user')->where('receive_id', $uid)->where('is_read', 0)->count();
|
||
|
||
$masterInfo = null;
|
||
if ($master) {
|
||
$masterInfo = [
|
||
'id' => (int)$master['id'],
|
||
'realname' => $master['realname'],
|
||
'verify_status' => $master['verify_status'],
|
||
'audit_msg' => $master['audit_msg'],
|
||
'work_status' => $master['work_status'],
|
||
'level' => (int)$master['level'],
|
||
'score' => (float)$master['score'],
|
||
'order_count' => (int)$master['order_count'],
|
||
'money' => (float)$master['money'],
|
||
'can_take' => $master['verify_status'] === 'pass',
|
||
];
|
||
}
|
||
|
||
$merchantRel = $this->merchantOf($uid);
|
||
$merchant = $merchantRel ? $merchantRel['merchant'] : null;
|
||
$merchantRole = $merchantRel ? $merchantRel['role'] : null;
|
||
$merchantInfo = null;
|
||
if ($merchant) {
|
||
$memberCount = (int)Db::name('merchant_member')->where('merchant_id', (int)$merchant['id'])->count();
|
||
$merchantInfo = [
|
||
'id' => (int)$merchant['id'],
|
||
'name' => $merchant['name'],
|
||
'legal_person' => isset($merchant['legal_person']) ? (string)$merchant['legal_person'] : '',
|
||
'legal_mobile' => isset($merchant['legal_mobile']) ? (string)$merchant['legal_mobile'] : '',
|
||
'tech_mobile' => isset($merchant['tech_mobile']) ? (string)$merchant['tech_mobile'] : '',
|
||
'verify_status' => $merchant['verify_status'],
|
||
'audit_msg' => $merchant['audit_msg'],
|
||
'settle_type' => $merchant['settle_type'],
|
||
'role' => $merchantRole,
|
||
'is_owner' => $merchantRole === 'owner',
|
||
'can_order' => $merchant['verify_status'] === 'pass',
|
||
'member_count' => $memberCount,
|
||
];
|
||
}
|
||
|
||
// 统计发单端各状态订单数量(与 Order::index 共享同一套团队归属口径)
|
||
$team = $this->currentMerchantTeam();
|
||
$teamUids = $team ? $team['user_ids'] : [$uid];
|
||
$statusGroups = Db::name('order')
|
||
->field('status, count(*) as cnt')
|
||
->where('user_id', 'in', $teamUids)
|
||
->where('user_deleted', 0)
|
||
->group('status')
|
||
->select();
|
||
$rawCounts = [];
|
||
foreach ($statusGroups as $sg) {
|
||
$rawCounts[(int)$sg['status']] = (int)$sg['cnt'];
|
||
}
|
||
$getCnt = function ($st) use ($rawCounts) {
|
||
return isset($rawCounts[$st]) ? $rawCounts[$st] : 0;
|
||
};
|
||
$orderCounts = [
|
||
'pending_pay' => $getCnt(0),
|
||
'pending_accept' => $getCnt(1),
|
||
'quoting' => $getCnt(2),
|
||
'scheduled' => $getCnt(3),
|
||
'serving' => $getCnt(4) + $getCnt(5) + $getCnt(6),
|
||
'pending_verify' => $getCnt(7),
|
||
];
|
||
|
||
if ($merchantRole === 'member' && $merchant && $merchant['verify_status'] === 'pass') {
|
||
$userType = 'merchant';
|
||
} elseif (!empty($user['user_type']) && in_array($user['user_type'], ['personal', 'merchant'], true)) {
|
||
$userType = $user['user_type'];
|
||
} else {
|
||
$userType = ($merchant && $merchant['verify_status'] === 'pass') ? 'merchant' : 'personal';
|
||
}
|
||
|
||
return [
|
||
'id' => (int)$user['id'],
|
||
'nickname' => $user['nickname'] ?: ('用户' . substr((string)$user['mobile'], -4)),
|
||
'mobile' => $user['mobile'],
|
||
'email' => !empty($user['email']) ? (string)$user['email'] : ($merchant && !empty($merchant['email']) ? (string)$merchant['email'] : ''),
|
||
'contact_address' => !empty($user['contact_address']) ? (string)$user['contact_address'] : ($merchant ? (string)($merchant['province'] . $merchant['city'] . $merchant['district'] . $merchant['address']) : ''),
|
||
'user_type' => $userType,
|
||
'company_name' => $merchant ? (string)$merchant['name'] : '',
|
||
'legal_person' => $merchant && isset($merchant['legal_person']) ? (string)$merchant['legal_person'] : '',
|
||
'legal_mobile' => $merchant && isset($merchant['legal_mobile']) ? (string)$merchant['legal_mobile'] : '',
|
||
'tech_mobile' => $merchant && isset($merchant['tech_mobile']) ? (string)$merchant['tech_mobile'] : '',
|
||
'avatar' => $user['avatar'] ?: '',
|
||
'balance' => (float)$user['money'],
|
||
'coupon_count' => (int)$couponCount,
|
||
'unread_count' => (int)$unread,
|
||
'order_counts' => $orderCounts,
|
||
'is_master' => $master ? true : false,
|
||
'master' => $masterInfo,
|
||
'is_merchant' => $merchant ? true : false,
|
||
'merchant' => $merchantInfo,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 保存账号信息(对标奇兵到家「账号信息」页:支持 家庭/个人 与 商家 两种用户类型)
|
||
* @ApiMethod (POST)
|
||
*/
|
||
public function saveAccount()
|
||
{
|
||
$uid = $this->uid();
|
||
$userType = trim((string)$this->request->post('user_type', 'personal'));
|
||
if (!in_array($userType, ['personal', 'merchant'], true)) {
|
||
$userType = 'personal';
|
||
}
|
||
$nickname = trim((string)$this->request->post('nickname', ''));
|
||
$mobile = trim((string)$this->request->post('mobile', ''));
|
||
$contactAddress = trim((string)$this->request->post('contact_address', ''));
|
||
$email = trim((string)$this->request->post('email', ''));
|
||
$companyName = trim((string)$this->request->post('company_name', ''));
|
||
$legalPerson = trim((string)$this->request->post('legal_person', ''));
|
||
$legalMobile = trim((string)$this->request->post('legal_mobile', ''));
|
||
$techMobile = trim((string)$this->request->post('tech_mobile', ''));
|
||
|
||
if ($nickname === '') {
|
||
$this->error('请输入用户昵称');
|
||
}
|
||
if ($legalPerson !== '' && preg_match('/\d/', $legalPerson)) {
|
||
$this->error('法人姓名不支持数字输入');
|
||
}
|
||
if ($legalMobile !== '' && !preg_match('/^1\d{10}$/', $legalMobile)) {
|
||
$this->error('法人手机号格式不正确');
|
||
}
|
||
if ($techMobile !== '' && !preg_match('/^1\d{10}$/', $techMobile)) {
|
||
$this->error('技术手机号格式不正确');
|
||
}
|
||
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
$this->error('电子邮箱格式不正确');
|
||
}
|
||
|
||
$merchantRel = $this->merchantOf($uid);
|
||
if ($userType === 'merchant') {
|
||
if (!$merchantRel || ($merchantRel['merchant']['verify_status'] ?? '') !== 'pass') {
|
||
$this->error('请先提交企业入驻申请并通过审核后再启用商家账号');
|
||
}
|
||
}
|
||
|
||
$userUpdate = [
|
||
'nickname' => $nickname,
|
||
'user_type' => $userType,
|
||
'contact_address' => $contactAddress,
|
||
'email' => $email,
|
||
'updatetime' => time(),
|
||
];
|
||
if ($mobile !== '' && preg_match('/^1\d{10}$/', $mobile)) {
|
||
$conflict = Db::name('user')->where('mobile', $mobile)->where('id', '<>', $uid)->find();
|
||
if ($conflict) {
|
||
$this->error('该联系号码已被其他账号绑定');
|
||
}
|
||
$userUpdate['mobile'] = $mobile;
|
||
}
|
||
Db::name('user')->where('id', $uid)->update($userUpdate);
|
||
|
||
if ($userType === 'merchant' && $merchantRel && $merchantRel['role'] === 'owner') {
|
||
$existMerchant = $merchantRel['merchant'];
|
||
$curUser = Db::name('user')->where('id', $uid)->find();
|
||
$merchantSave = [
|
||
'name' => $companyName !== '' ? $companyName : (!empty($existMerchant['name']) ? $existMerchant['name'] : $nickname),
|
||
'legal_person' => $legalPerson,
|
||
'legal_mobile' => $legalMobile,
|
||
'tech_mobile' => $techMobile,
|
||
'contact' => $nickname,
|
||
'mobile' => $curUser ? (string)$curUser['mobile'] : '',
|
||
'address' => $contactAddress,
|
||
'email' => $email,
|
||
'updatetime' => time(),
|
||
];
|
||
Db::name('merchant')->where('id', (int)$existMerchant['id'])->update($merchantSave);
|
||
}
|
||
|
||
$this->success('保存成功', ['userinfo' => $this->profileData()]);
|
||
}
|
||
|
||
/**
|
||
* 微信授权登录(小程序 uni.login 拿到的 code)
|
||
* @ApiMethod (POST)
|
||
*/
|
||
public function wxlogin()
|
||
{
|
||
$code = trim((string)$this->request->post('code'));
|
||
$nickname = trim((string)$this->request->post('nickname'));
|
||
$avatar = trim((string)$this->request->post('avatar'));
|
||
if ($code === '') {
|
||
$this->error('缺少微信登录凭证 code');
|
||
}
|
||
|
||
$wx = \app\common\library\WxLogin::jscode2session($code);
|
||
if (isset($wx['error'])) {
|
||
$this->error($wx['error'], ['wx_errcode' => isset($wx['errcode']) ? (int)$wx['errcode'] : 0]);
|
||
}
|
||
$ret = \app\common\library\WxLogin::provision(
|
||
$wx['openid'],
|
||
isset($wx['unionid']) ? $wx['unionid'] : '',
|
||
isset($wx['session_key']) ? $wx['session_key'] : '',
|
||
$nickname,
|
||
$avatar
|
||
);
|
||
$this->auth->direct($ret['user_id']);
|
||
|
||
$this->success('登录成功', [
|
||
'token' => $this->auth->getToken(),
|
||
'expires' => 2592000,
|
||
'userinfo' => $this->profileData(),
|
||
'is_new' => $ret['is_new'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 切换角色(用户 / 师傅)
|
||
* @ApiMethod (POST)
|
||
*/
|
||
|
||
/**
|
||
* 微信手机号授权快捷登录
|
||
* @ApiMethod (POST)
|
||
*/
|
||
public function wxPhoneLogin()
|
||
{
|
||
$phoneCode = trim((string)$this->request->post('phone_code'));
|
||
$loginCode = trim((string)$this->request->post('login_code'));
|
||
if ($phoneCode === '') {
|
||
$this->error('缺少微信手机号授权凭证');
|
||
}
|
||
|
||
// 1. 换取手机号
|
||
$phoneInfo = \app\common\library\WxLogin::getPhoneNumber($phoneCode);
|
||
if (isset($phoneInfo['error'])) {
|
||
$this->error('获取微信手机号失败:' . $phoneInfo['error'], ['wx_errcode' => isset($phoneInfo['errcode']) ? (int)$phoneInfo['errcode'] : 0]);
|
||
}
|
||
$mobile = isset($phoneInfo['purePhoneNumber']) ? (string)$phoneInfo['purePhoneNumber'] : (string)$phoneInfo['phoneNumber'];
|
||
if (!$mobile) {
|
||
$this->error('解析手机号失败');
|
||
}
|
||
|
||
// 2. 如果传了 login_code,换取 openid
|
||
$openid = '';
|
||
$sessionKey = '';
|
||
$unionid = '';
|
||
if ($loginCode !== '') {
|
||
$wx = \app\common\library\WxLogin::jscode2session($loginCode);
|
||
if (!isset($wx['error']) && !empty($wx['openid'])) {
|
||
$openid = $wx['openid'];
|
||
$sessionKey = isset($wx['session_key']) ? $wx['session_key'] : '';
|
||
$unionid = isset($wx['unionid']) ? $wx['unionid'] : '';
|
||
}
|
||
}
|
||
|
||
// 3. 落库与关联登录
|
||
$ret = \app\common\library\WxLogin::provisionWithMobile(
|
||
$mobile,
|
||
$openid,
|
||
$unionid,
|
||
$sessionKey
|
||
);
|
||
|
||
$this->auth->direct($ret['user_id']);
|
||
|
||
$this->success('登录成功', [
|
||
'token' => $this->auth->getToken(),
|
||
'expires' => 2592000,
|
||
'userinfo' => $this->profileData(),
|
||
'is_new' => $ret['is_new'],
|
||
'mobile' => $mobile,
|
||
]);
|
||
}
|
||
|
||
public function switchRole()
|
||
{
|
||
$role = $this->request->post('role', 'user');
|
||
if (!in_array($role, ['user', 'master', 'merchant'], true)) {
|
||
$this->error('角色参数错误');
|
||
}
|
||
|
||
if ($role === 'master') {
|
||
$master = $this->masterInfo();
|
||
if (!$master) {
|
||
$this->error('请先完成师傅入驻', ['need_apply' => 1], 2001);
|
||
}
|
||
if ($master['verify_status'] === 'pending') {
|
||
$this->error('师傅资质审核中,请耐心等待', ['verify_status' => 'pending'], 2002);
|
||
}
|
||
if ($master['verify_status'] === 'reject') {
|
||
$this->error('资质未通过:' . $master['audit_msg'], ['verify_status' => 'reject'], 2003);
|
||
}
|
||
}
|
||
|
||
if ($role === 'merchant') {
|
||
$merchantRel = $this->merchantOf($this->uid());
|
||
$merchant = $merchantRel ? $merchantRel['merchant'] : null;
|
||
if (!$merchant) {
|
||
$this->error('请先完成商家入驻', ['need_apply' => 1], 2001);
|
||
}
|
||
if ($merchant['verify_status'] === 'pending') {
|
||
$this->error('商家资质审核中,请耐心等待', ['verify_status' => 'pending'], 2002);
|
||
}
|
||
if ($merchant['verify_status'] === 'reject') {
|
||
$this->error('资质未通过:' . $merchant['audit_msg'], ['verify_status' => 'reject'], 2003);
|
||
}
|
||
}
|
||
|
||
$redirect = '/pages/index/index';
|
||
if ($role === 'master') {
|
||
$redirect = '/pages/master/hall';
|
||
} elseif ($role === 'merchant') {
|
||
$redirect = '/pages/mine/mine';
|
||
}
|
||
|
||
$this->success('切换成功', ['role' => $role, 'redirect' => $redirect]);
|
||
}
|
||
|
||
/**
|
||
* 当前用户的商家归属:主账号 / 关联账号
|
||
* 返回 ['merchant' => 商家行, 'role' => owner|member]
|
||
*/
|
||
protected function merchantOf($uid)
|
||
{
|
||
$merchant = Db::name('merchant')->where('user_id', $uid)->find();
|
||
if ($merchant) {
|
||
return ['merchant' => $merchant, 'role' => 'owner'];
|
||
}
|
||
$bind = Db::name('merchant_member')->where('user_id', $uid)->find();
|
||
if ($bind) {
|
||
$merchant = Db::name('merchant')->where('id', (int)$bind['merchant_id'])->find();
|
||
if ($merchant) {
|
||
return ['merchant' => $merchant, 'role' => 'member'];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|