// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\model; use app\common\enum\user\balanceLog\Scene as SceneEnum; use app\common\library\helper; use app\store\model\user\BalanceLog as BalanceLogModel; use think\facade\Cache; use think\facade\Db; use app\api\service\User as UserService; use app\api\model\UserOauth as UserOauthModel; use app\common\model\User as UserModel; use cores\exception\BaseException; use yiovo\captcha\facade\CaptchaApi; /** * 用户模型类 * Class User * @package app\api\model */ class User extends UserModel { /** * 隐藏字段 * @var array */ protected $hidden = [ 'open_id', 'is_delete', 'store_id', 'create_time', 'update_time' ]; /** * 获取器:隐藏手机号中间四位 * @param string $value * @return string */ public function getMobileAttr(string $value): string { return strlen($value) === 11 ? hide_mobile($value) : $value; } /** * 获取用户信息 * @param string $token * @return User|array|false|null * @throws BaseException */ public static function getUserByToken(string $token) { // 检查登录态是否存在 if (!Cache::has($token)) { return false; } // 用户的ID $userId = (int)Cache::get($token)['user']['user_id']; // 用户基本信息 $userInfo = self::detail($userId); if (empty($userInfo) || $userInfo['is_delete']) { throwError('很抱歉,用户信息不存在或已删除', config('status.not_logged')); } // 获取用户关联的第三方用户信息(当前客户端) try { $userInfo['currentOauth'] = UserOauthModel::getOauth($userId, getPlatform()); } catch (\Throwable $e) { throwError($e->getMessage()); } return $userInfo; } /** * 绑定手机号(当前登录用户) * @param array $data * @return bool * @throws BaseException */ public function bindMobile(array $data): bool { // 当前登录的用户信息 $userInfo = UserService::getCurrentLoginUser(true); // 验证绑定的手机号 $this->checkBindMobile($data); // 更新手机号记录 return $userInfo->save(['mobile' => $data['mobile']]); } /** * 修改个人信息(头像昵称) * @param array $form * @return bool * @throws BaseException */ public function personal(array $form): bool { // 当前登录的用户信息 $userInfo = UserService::getCurrentLoginUser(true); // 默认数据 $data['avatar_id'] = $form['avatarId'] ?: $userInfo['avatar_id']; $data['nick_name'] = $form['nickName'] ?: $userInfo['nick_name']; // 更新用户记录 return $userInfo->save($data); } /** * 保存实名信息(姓名 / 身份证号 / 车牌号) * * 分油是实名制的:上游在分油前会做「姓名 + 身份证号」二要素认证, * 所以这三项必须在下单前补全。车牌号只在本地留存与后台展示,不上送上游。 * * @param array $form * @return bool * @throws BaseException */ public function realname(array $form): bool { $realName = trim((string)($form['realName'] ?? '')); $idCard = strtoupper(trim((string)($form['idCard'] ?? ''))); $plateNo = strtoupper(trim((string)($form['plateNo'] ?? ''))); if ($realName === '') { $this->error = '请填写真实姓名'; return false; } // 中文姓名 2~20 字,允许少数民族姓名里的间隔号 if (!preg_match('/^[\x{4e00}-\x{9fa5}][\x{4e00}-\x{9fa5}·]{1,19}$/u', $realName)) { $this->error = '姓名格式不正确,请填写身份证上的中文姓名'; return false; } if (!preg_match('/^\d{17}[\dX]$/', $idCard)) { $this->error = '身份证号格式不正确(18位)'; return false; } // 省份简称 + 城市字母(不含 I/O,避免与 1/0 混淆) + 5~7 位序号(含新能源 8 位车牌) if (!preg_match('/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,6}$/u', $plateNo)) { $this->error = '车牌号格式不正确'; return false; } // 当前登录的用户信息 $userInfo = UserService::getCurrentLoginUser(true); // 身份证号一旦用于分油就不允许再改:上游按身份证号累计司机额度, // 改了会导致历史订单的额度与司机对不上。姓名/车牌可以改。 if ((string)$userInfo['id_card'] !== '' && (string)$userInfo['id_card'] !== $idCard) { $used = Db::name('partners_order') ->where('user_id', '=', (int)$userInfo['user_id']) ->where('source', '=', 'mall') ->count(); if ($used > 0) { $this->error = '身份证号已用于分油,不能修改,请联系客服'; return false; } } return $userInfo->save([ 'real_name' => $realName, 'id_card' => $idCard, 'plate_no' => $plateNo, ]) !== false; } /** * 实名信息是否已补全(下单前的必要条件) * @param array|static $user * @return bool */ public static function isRealnameReady($user): bool { return trim((string)($user['real_name'] ?? '')) !== '' && trim((string)($user['id_card'] ?? '')) !== '' && trim((string)($user['plate_no'] ?? '')) !== ''; } /** * 验证绑定的手机号 * @param array $data * @return void * @throws BaseException */ private function checkBindMobile(array $data): void { // 验证短信验证码是否匹配 if (!CaptchaApi::checkSms($data['smsCode'], $data['mobile'])) { throwError('短信验证码不正确'); } // 判断手机号是否已存在 if (static::checkExistByMobile($data['mobile'])) { throwError('很抱歉,该手机号已绑定其他账户'); } } /** * 用户充值:余额 * @param string $storeUserName * @param array $data * @return bool */ public function rechargeToBalance(string $storeUserName, array $data) { if (!isset($data['money']) || $data['money'] === '' || $data['money'] < 0) { $this->error = '请输入正确的金额'; return false; } // 判断充值方式,计算最终金额 if ($data['mode'] === 'inc') { $diffMoney = $data['money']; } elseif ($data['mode'] === 'dec') { $diffMoney = -$data['money']; } else { $diffMoney = helper::bcsub($data['money'], $this['balance']); } // 更新记录 $this->transaction(function () use ($storeUserName, $data, $diffMoney) { // 更新账户余额 static::setIncBalance((int)$this['user_id'], (float)$diffMoney); // 新增余额变动记录 BalanceLogModel::add(SceneEnum::SHARE, [ 'user_id' => $this['user_id'], 'money' => (float)$diffMoney, 'remark' => $data['remark'], ], [$storeUserName]); }); return true; } }