scene('wx_applets')->check($params)) { return $this->json(4000, $validate->getError()); } $minApp = WechatApplets::getInstance(); $jsCode = $params['code']; $wxUser = $minApp->auth->session($jsCode); if (isset($wxUser['errcode']) && $wxUser['errcode'] != 0) { return $this->json(4001, $wxUser['errcode'].';'.$wxUser['errmsg'] ?? '登录失败'); } // $wxUser success has [session_key, openid, unionid] // 有效期2小时 $wxUser['expire_time'] = time() + 7200; $wxUser['session_key'] = $wxUser['session_key'] ?? ''; $openID = $wxUser['openid']; $unionid = $wxUser['unionid'] ?? ''; if (empty($openID) && empty($unionid)) { return $this->json(4002, '登录失败'); } $isActive = $params['is_active'] ?? 0; $isActive = (is_numeric($isActive) && $isActive > 0) ? AccountRepository::BOOL_TRUE : AccountRepository::BOOL_FALSE; $phoneActive = $params['phone_active'] ?? 0; $phoneActive = (is_numeric($phoneActive) && $phoneActive > 0) ? AccountRepository::BOOL_TRUE : AccountRepository::BOOL_FALSE; try { $repo = AccountRepository::getInstance(); $account = $repo->findByOpenID($openID); if (!$account && !empty($unionid)) { $account = $repo->findByUnionId($unionid); } $now = date('Y-m-d H:i:s'); if (!$account) { // 自动注册 $account = $repo->create([ 'unionid' => $unionid ?? '', 'openid' => $openID, 'last_login' => $now, 'login_ip' => $this->request->ip(), 'created_at' => $now, 'nickname' => $params['nickname'] ?? '', 'headimgurl' => $params['headimgurl'] ?? '', 'mobile' => $params['mobile'] ?? '', 'status' => AccountRepository::STATUS_NORMAL, 'is_active' => $isActive, 'phone_active' => $phoneActive, 'session_key' => $wxUser['session_key'] ?? '', ]); } else { $updateData = [ 'last_login' => date('Y-m-d H:i:s'), 'login_ip' => $this->request->ip(), 'session_key' => $wxUser['session_key'] ?? '', ]; // 更新资料 $modifyStringList = ['headimgurl', 'nickname', 'mobile']; foreach ($modifyStringList as $modifyKey) { if (isset($account[$modifyKey]) && empty($account[$modifyKey])) { $updateData[$modifyKey] = $params[$modifyKey] ?? ''; } } if (empty($account['gender'])) { $updateData['gender'] = $params['gender'] ?? 0; } if (isset($account['is_active']) && $account['is_active'] == AccountRepository::BOOL_FALSE) { $updateData['is_active'] = $isActive; } if (isset($account['phone_active']) && $account['phone_active'] == AccountRepository::BOOL_FALSE) { $updateData['phone_active'] = $phoneActive; } $repo->update($updateData, ['id' => $account['id']]); $account = $repo->findById($account['id']); } } catch (RepositoryException | Exception $e) { return $this->json(4003, '登录失败!'.$e->getMessage()); } $account = $account->toArray(); $jwtData = [ 'user_id' => $account['id'], 'open_id' => $openID, 'session_key' => $wxUser['session_key'], 'expire_time' => $wxUser['expire_time'], ]; $account['headimgurl'] = File::convertCompleteFileUrl($account['headimgurl']); $fields = [ 'coding', 'real_name', 'nickname', 'headimgurl', 'gender', 'mobile', 'status', 'is_active', 'phone_active' ]; $accountData = arrayKeysFilter($account, $fields); $data = [ 'account_id' => $account['id'], 'token' => Jwt::generate($jwtData), 'expire' => $wxUser['expire_time'], 'openid' => $openID, ]; $data = array_merge($data, $accountData); return $this->json(0, 'success', $data); } /** * 获取用户信息 * * @return Json */ public function info(): Json { try { $accountId = $this->request->user['user_id'] ?? 0; $user = Account::getUser($accountId); return $this->json(0, 'success', $user); } catch (Exception $e) { return $this->json(4000, '没有相关的用户记录'.$e->getMessage()); } } /** * 修改用户信息 */ public function updateInfo(): Json { try { $params = input('post.'); $rules = [ 'field|修改项' => 'require|in:nickname', 'value|修改内容' => 'require', ]; $accountId = $this->request->user['user_id'] ?? 0; $validate = $this->validateByApi($params, $rules, ['field.in' => '参数错误']); if ($validate !== true) { return $validate; } if (!$customer = Account::findById($accountId)) { return $this->json(4004, '用户不存在'); } $customer->save([ $params['field'] => $params['value'] ]); } catch (Exception $e) { Log::error('修改用户信息失败'.$e->getMessage()); return $this->json(5000, '修改失败!'.$e->getMessage()); } return $this->json(); } /** * 临时登录 通过openid登录 仅用于接口测试阶段 * * @return Json * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function tempLogin(): Json { $params = $this->request->param(); if (!isset($params['openid'])) { return $this->json(4001, '参数错误'); } if (!$user = AccountRepository::getInstance()->findByOpenID($params['openid'])) { return $this->json(4004, '账号不存在'); } $data = [ 'token' => Jwt::generate(['user_id' => $user['id'], 'nickname' => $user['nickname']]), 'expire' => Jwt::expire() ]; return $this->json(0, 'success', $data); } /** * 用户操作记录 分享、咨询 * * @return Json * @throws Exception */ public function record(): Json { if (!$this->request->isPost()) { return $this->json(4000, '无效请求'); } $accountId = $this->request->user['user_id'] ?? 0; $params = input('post.'); $rules = [ 'type|操作类型' => 'require|in:content,other,spu,activity', 'action|操作' => 'require', ]; if (isset($params['type']) && $params['type'] == AccountRecord::TYPE_CONTENT) { $rules['id|ID'] = 'require'; } $validate = $this->validateByApi($params, $rules, ['type.in' => '类型错误', 'id.require' => '此类型 ID必传']); if ($validate !== true) { return $validate; } try { $relationId = $params['id'] ?? 0; $relationId = is_numeric($relationId) ? $relationId : 0; AccountRecord::record($accountId, $params['type'], $params['action'], $relationId); } catch (Exception $e) { AccountRepository::log('记录用户操作失败', $e); return $this->json(5001, '操作失败'); } return $this->json(); } /** * 绑定手机 * * @return bool|Json * @throws Exception */ public function bindPhone() { if (!$this->request->isPost()) { return $this->json(4000, '无效请求'); } $accountId = $this->request->user['user_id'] ?? 0; $params = input('post.'); $rules = [ 'encryptedData|加密数据' => 'require', 'iv|IV' => 'require', ]; $validate = $this->validateByApi($params, $rules); if ($validate !== true) { return $validate; } try { if (!$account = Account::findById($accountId)) { return $this->json(4000, '用户不存在'); } // 解密手机相关数据 若存在手机则覆盖 $minApp = WechatApplets::getInstance(); $sessionKey = $this->request->user['session_key'] ?? ''; $decryptData = $minApp->encryptor->decryptData($sessionKey, $params['iv'], $params['encryptedData']); $phone = $decryptData['phoneNumber'] ?? ''; // 通过iv和加密数据 解密出手机号 if (Account::where('id', '<>', $accountId)->where('mobile', $phone)->count() > 0) { return $this->json(4000, '该手机已被绑定,若有绑定错误,请联系客服'); } if ($phone) { $account->save(['mobile' => $phone, 'phone_active' => Account::COMMON_ON]); } return $this->json(0, 'success', ['phone' => $phone]); } catch (Exception $e) { AccountRepository::log('手机绑定失败', $e); return $this->json(5001, '手机绑定失败'); } } // 打卡页面信息 public function signInfo(): Json { $list = []; $week = ['日', '一', '二', '三', '四', '五', '六']; $info = [ 'today' => date('Y年m月d日'), 'week' => '星期'.$week[date('w')], 'now' => date('H:i:s'), 'is_sign' => (int) !empty($list), ]; return $this->json(0, 'success', ['info' => $info]); } /** * 今日打卡记录 * * @return \think\response\Json * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function signToday(): Json { $accountId = $this->request->user['user_id'] ?? 0; $list = []; $buttonColor = 'gray'; if ($accountId > 0) { if (!$account = Account::findById($accountId)) { return $this->json(6001, '请先登录'); } $where = []; $where[] = ['cl.created_at', '>', date('Y-m-d 00:00:00')]; $where[] = ['cl.created_at', '<', date('Y-m-d 23:59:59')]; $where[] = ['cl.account_id', '=', $accountId]; $where[] = ['cl.role', '=', $account['role']]; $limit = $account['role'] == Account::ROLE_NORMAL ? 4 : 0;//普通用户只展示最新4条 其他不限制 $list = \app\model\ClockLog::alias('cl') ->leftJoin('worksite w', 'w.id = cl.worksite_id') ->field('cl.*,w.name as worksite_name') ->where($where) ->limit($limit) ->order('cl.id', 'desc') ->select(); /** * 打卡按钮颜色 gray=灰色 orange=橙色 绿色=green * 普通人 只有灰色 和 绿色 未打卡灰色 打卡>0为绿色 * 工人和负责人 * 灰色: 未打卡、(中途休息期间如12~14 未判断此条件) * 橙色:上班卡数量>下班卡数量且>0 * 绿色:1. 下班卡数量>= 上班卡数量 且下班卡数量>=1 或 2.下午下班卡打了后 */ if ($account['role'] == Account::ROLE_NORMAL) { $buttonColor = $list->count() > 0 ? 'green' : 'gray'; } else { $array = $list->whereIn('status', [0, 1])->column('type'); $onCount = 0;//上班卡数量 $offCount = 0;//下班卡数量 foreach ($array as $sign) { if (strpos($sign, '_on')) { $onCount++; } if (strpos($sign, '_off')) { $offCount++; } } if (in_array('afternoon_off', $array) || ($offCount >= $onCount && $offCount >= 1)) { $buttonColor = 'green'; } if (($offCount >= $onCount && $offCount >= 1) || $onCount > 0) { $buttonColor = 'orange'; } } $list->each(function ($item) { $item->type_text = ClockLog::typeText()[$item->type]; switch ($item->status) { case 0: $item->status_text = '待确认'; break; case 1: $item->status_text = '已确认'; break; case -1: $item->status_text = '不通过'; break; } $item->time = date('H:i', $item->create_time); }); $list = $list->toArray(); } return $this->json(0, 'success', ['buttonColor' => $buttonColor, 'list' => $list]); } public function checkActive(): Json { try { $openid = input('openid/s'); if (empty($openid)) { return $this->json(0, 'success', ['status' => 0]); } $isActive = (int) Account::where('openid', $openid)->value('is_active'); return $this->json(0, 'success', ['status' => $isActive]); } catch (Exception $e) { return $this->json(4000, '检查账号是否微信授权是不'.$e->getMessage()); } } /** * 月度打卡记录 * { * 'ok':[2,5,6,8,12], * 'add':[4,7,11,22], * 'no':[19,28] * } * * @return \think\response\Json * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function monthSignLog(): Json { $accountId = $this->request->user['user_id'] ?? 0; $date = input('date/s', ''); $date = $date ?: date('Y-m'); $ym = str_replace('-', '', $date); $res = [ 'ok' => [], //正常打卡:普通用户当天有打卡=正常 工人和负责人:打满4次=正常 'add' => [],// 补打卡:仅限工人和负责人 当天存在补卡 'no' => [],// 非正常打卡:仅限工人和负责人 4>打卡次数>0 ]; if ($accountId > 0) { if (!$account = Account::findById($accountId)) { return $this->json(6001, '请先登录'); } $where = []; $where[] = ['cl.day', 'like', $ym.'%']; $where[] = ['cl.account_id', '=', $accountId]; $where[] = ['cl.role', '=', $account['role']]; $list = \app\model\ClockLog::alias('cl') ->where($where) ->select(); $signNum = [];//每日打卡次数 foreach ($list as $item) { $day = (int) substr($item['day'], -2); if (!isset($signNum[$day])) { $signNum[$day] = 0; } $signNum[$day]++; if ($item['is_replenish'] == ClockLog::COMMON_ON) { $res['add'][] = $day; } } foreach ($signNum as $day => $count) { if ($account['role'] == Account::ROLE_NORMAL) { if ($count >= 1) { $res['ok'][] = $day; } } else { if ($count == 4) { $res['ok'][] = $day; } else { $res['no'][] = $day; } } } $res['ok'] = array_unique($res['ok']); $res['add'] = array_unique($res['add']); $res['no'] = array_unique($res['no']); } return $this->json(0, 'success', $res); } /** * 打卡 * 普通用户打卡不需要任何参数 * 员工和负责人打卡 参数相同 */ public function sign(): Json { try { $accountId = $this->request->user['user_id'] ?? 0; if (!$customer = Account::findById($accountId)) { return $this->json(6001, '请先登录'); } $input = input('post.'); // 工人打卡 if ($customer['role'] == Account::ROLE_NORMAL && $this->normalSign($accountId, $input['type'])) { return $this->json(); } $rules = [ 'type|打卡类型' => 'require|in:morning_on,morning_off,afternoon_on,afternoon_off', 'lat|维度' => 'require', 'lng|经度' => 'require', 'worksite_id|工地' => 'require|number', ]; $validate = $this->validateByApi($input, $rules, ['type.in' => '打卡类型错误']); if ($validate !== true) { return $validate; } Config::load('extra/base', 'base'); $baseConfig = config('base'); $signArea = $baseConfig['sign_area'] ?? 200; $worksite = Worksite::getNearest($input['lng'], $input['lat'], $signArea); if (empty($worksite) || $worksite['id'] != $input['worksite_id']) { return $this->json(4004, '不在打卡范围!'); } $time = time(); // $time = $time - 86401 * 3; $now = date('Y-m-d H:i:s', $time); $day = date('Ymd', $time); if (ClockLog::hasSign($accountId, $input['type'], $input['worksite_id'])) { return $this->json(4001, '今日已打过此卡'); } $data = [ 'account_id' => $accountId, 'type' => $input['type'], 'worksite_id' => $input['worksite_id'], 'created_at' => $now, 'create_time' => $time, 'day' => $day, 'role' => $customer['role'], 'indexs' => $accountId.'-'.$input['worksite_id'].'-'.$day, ]; // 工人 if ($customer['role'] == Account::ROLE_WORKER) { $data['need_statistic'] = Account::COMMON_ON; } // 负责人 if ($customer['role'] == Account::ROLE_MANAGER) { $data['status'] = Account::COMMON_ON; $data['need_statistic'] = Account::COMMON_OFF; } ClockLog::create($data); // 创建当日工资初始记录 PayLog::createWhenNotExists($accountId, $input['worksite_id'], $day); } catch (ApiException $e) { return $this->json(4000, $e->getMessage()); } catch (Exception $e) { Log::error('打卡失败'.$e->getMessage()); return $this->json(5000, '打卡失败!'); } return $this->json(); } /** * 普通用户打卡 * * @param int $accountId * @param string $type * @return bool * @throws \app\exception\ApiException * @throws \think\db\exception\DbException */ private function normalSign(int $accountId, string $type): bool { $time = time(); // $time = $time - 86401 * 3; $now = date('Y-m-d H:i:s', $time); $day = date('Ymd', $time); if (ClockLog::checkRate($accountId, $type)) { throw new ApiException('打卡频率过快!'); } ClockLog::create([ 'account_id' => $accountId, 'type' => $type, 'created_at' => $now, 'create_time' => $time, 'day' => $day, 'status' => ClockLog::COMMON_ON, ]); return true; } }