selectField = [ 'id', 'open_id', 'avatar', 'nick_name', 'password', 'phone', 'email', 'code', 'role_id', 'province_id', 'city_id', 'reg_ip', 'last_login_time', 'ip', 'ip_table', 'operation_password', 'desc', 'status', 'created_at', 'updated_at', 'deleted_at', ]; $this->model = AdminModel::class; $this->queryField = [ 'phone' => 'like', 'nick_name' => 'like', 'email' => 'like', 'role_id' => '=', 'status' => '=', ]; } /** * 获取用户列表 * @return array * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function list(): array { $this->with = [ 'role' ]; $result = $this->getPageList(); foreach ($result['items'] as &$v) { $v['ip_table'] = json_decode($v['ip_table'], true); $v['status_text'] = UserStatusEnum::from($v['status'])->description(); } return $result; } public function detail($id) { $result = $this->getDetail($id); $result['ip_table'] = json_decode($result['ip_table'], true); $result['status_text'] = UserStatusEnum::from($result['status'])->description(); return $result; } public function option() { $this->optionField = ['id', 'nick_name']; return $this->getOption(); } /** * 创建用户 * @param $params * @return mixed * @throws Exception */ public function create($params): mixed { $params['ip_table'] = json_encode([]); $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT); $existsUser = $this->model::where('phone', $params['phone'])->whereOr('email', $params['email'])->exists(); if ($existsUser) { $this->utils->errorThrow('账号或邮箱已存在'); } return $this->insert($params); } /** * 编辑用户 * @param $id * @param $params * @return mixed * @throws Exception */ public function update($id, $params): mixed { return $this->save($id, $params); } /** * 删除用户 * @param $ids * @return mixed|true * @throws Exception */ public function delete($ids): mixed { return $this->del($ids); } /** * 修改密码 * @param $password * @param $newPassword * @return mixed * @throws Exception */ public function changePassword($password, $newPassword): mixed { $info = $this->model::find($this->userId); if (empty($info)) { return $this->utils->notFound('数据不存在'); } if (!password_verify($password, $info['password'])) { return $this->utils->errorThrow('密码错误'); } if (password_verify($newPassword, $info['password'])) { return $this->utils->errorThrow('新密码不能与旧密码相同'); } $result = $this->model::where('id', $this->userId)->update([ 'password' => password_hash($newPassword, PASSWORD_DEFAULT), 'updated_at' => get_time(), ]); if (!$result) { return $this->utils->errorThrow('重置密码失败'); } return $result; } /** * 重置密码 * @param $id * @param $password * @param $newPassword * @return mixed * @throws Exception */ public function resetPassword($id, $password, $newPassword): mixed { if ($password !== $newPassword) { return $this->utils->errorThrow('两次密码不一致'); } $info = $this->model::find($id); if (empty($info)) { return $this->utils->notFound('数据不存在'); } if (password_verify($newPassword, $info['password'])) { return $this->utils->errorThrow('新密码不能与旧密码相同'); } $result = $this->model::where('id', $id)->update([ 'password' => password_hash($newPassword, PASSWORD_DEFAULT), 'updated_at' => get_time(), ]); if (!$result) { return $this->utils->errorThrow('重置密码失败'); } return $result; } /** * 获取用户信息(本人完整字段,供个人中心回填,不做手机号脱敏) * @return mixed * @throws Exception */ public function myInfo(): mixed { $this->with = [ 'role' ]; $result = $this->getDetail($this->userId); $result['created_day_at'] = format_time(strtotime($result['created_at']), 'Y-m-d'); // 兼容前端 Profile 组件字段 $result['realName'] = $result['nick_name'] ?? ''; $result['username'] = $result['phone'] ?? ''; return $result; } /** * 个人中心更新资料(昵称/头像/邮箱/备注/手机) * 为什么单独接口:不走管理员 CRUD,避免越权改角色等字段 */ public function updateProfile(array $params): mixed { $allow = ['nick_name', 'avatar', 'email', 'desc', 'phone']; $data = []; foreach ($allow as $field) { if (array_key_exists($field, $params)) { $data[$field] = is_string($params[$field]) ? trim($params[$field]) : $params[$field]; } } if (isset($data['nick_name']) && $data['nick_name'] === '') { $this->utils->errorThrow('昵称不能为空'); } if (isset($data['phone']) && $data['phone'] !== '') { $exists = $this->model::where('phone', $data['phone']) ->where('id', '<>', $this->userId) ->where('deleted_at', 0) ->exists(); if ($exists) { $this->utils->errorThrow('手机号已被占用'); } } if (isset($data['email']) && $data['email'] !== '') { $exists = $this->model::where('email', $data['email']) ->where('id', '<>', $this->userId) ->where('deleted_at', 0) ->exists(); if ($exists) { $this->utils->errorThrow('邮箱已被占用'); } } if (empty($data)) { return $this->myInfo(); } $data['updated_at'] = get_time(); $this->model::where('id', $this->userId)->update($data); return $this->myInfo(); } /** * 当前用户登录日志分页 */ public function loginLogList(): array { $page = max(1, (int) request()->get('page', 1)); $size = max(1, min(100, (int) request()->get('pageSize', request()->get('size', 10)))); $query = \App\Models\LoginLogModel::where('user_id', $this->userId)->orderByDesc('id'); $total = (clone $query)->count(); $items = $query->forPage($page, $size)->get()->toArray(); return [ 'page' => $page, 'size' => $size, 'page_count' => (int) ceil($total / max($size, 1)), 'total' => $total, 'items' => $items, ]; } /** * 当前用户操作日志分页(附带接口操作名) */ public function opLogList(): array { $page = max(1, (int) request()->get('page', 1)); $size = max(1, min(100, (int) request()->get('pageSize', request()->get('size', 10)))); $query = \App\Models\ApiOpLogModel::where('user_id', $this->userId)->orderByDesc('id'); $total = (clone $query)->count(); $items = $query->forPage($page, $size)->get()->toArray(); $urls = array_values(array_unique(array_column($items, 'url'))); $endpointMap = []; if (!empty($urls)) { $endpointMap = \App\Models\ApiEndpointModel::whereIn('url', $urls) ->where('deleted_at', 0) ->get(['url', 'name', 'description']) ->keyBy('url') ->toArray(); } foreach ($items as &$item) { $ep = $endpointMap[$item['url'] ?? ''] ?? null; $item['name'] = $ep['name'] ?? ''; $item['description'] = $ep['description'] ?? ''; $item['method_text'] = match ((int) ($item['method'] ?? 0)) { 1 => 'GET', 2 => 'POST', default => 'ANY', }; $item['type_text'] = ((int) ($item['type'] ?? 0) === 0) ? '成功' : '失败'; if (is_string($item['param'] ?? null)) { $item['param'] = json_decode($item['param'], true) ?: []; } if (is_string($item['result'] ?? null)) { $item['result'] = json_decode($item['result'], true) ?: []; } } unset($item); return [ 'page' => $page, 'size' => $size, 'page_count' => (int) ceil($total / max($size, 1)), 'total' => $total, 'items' => $items, ]; } /** * 获取菜单列表 * @return array */ public function menu(): array { if ($this->roleId !== 1) { // 从redis获取菜单列表 $redis = RedisService::getInstance()->init(config('nl.redis.menu_key'))->get($this->roleId); // $redis = null; if (!empty($redis)) { return json_decode($redis, true); } } // 如果没有数据则从数据库获取菜单列表 $selectField = [ 'id', 'pid', 'title', 'icon', 'name', 'affix_tab', 'path', 'component', 'redirect', 'keep_alive', 'hide_in_menu', 'badge', 'badge_type', 'badge_variants', 'iframe_src', 'sort', 'query', 'created_at' ]; if ($this->roleId === 1) { $menuList = MenuModel::where('deleted_at', 0)->orderBy('sort', 'asc')->get($selectField); } else { $menuIds = RoleMenuRelationModel::where('role_id', $this->roleId)->pluck('menu_id'); $menuList = MenuModel::where('deleted_at', 0)->whereIn('id', $menuIds)->orderBy('sort', 'asc')->get($selectField); } $resultMenus = []; foreach ($menuList as $menu) { $resultMenus[] = [ 'id' => $menu->id, 'name' => $menu->name, 'path' => $menu->path, 'component' => $menu->component, 'redirect' => $menu->redirect, 'pid' => $menu->pid, 'sort' => $menu->sort, 'meta' => [ 'title' => $menu->title, 'icon' => $menu->icon, 'keepAlive' => !$menu->keep_alive, 'hideInMenu' => (bool)$menu->hide_in_menu, 'affixTab' => !$menu->affix_tab, 'order' => $menu->sort, 'iframeSrc' => $menu->iframe_src, ] ]; } $result = $this->utils->tree($resultMenus); // 根据sort排序(多层) usort($result, function ($a, $b) { return $a['meta']['order'] - $b['meta']['order']; }); // 缓存菜单列表到redis RedisService::getInstance()->init(config('nl.redis.menu_key'))->set($this->roleId, json_encode($result), 3600); return $result; } }