Files
nl-admin-api/app/Service/AdminService.php
2026-08-10 18:32:06 +08:00

369 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service;
use App\BaseApp\BaseService;
use App\Enum\UserStatusEnum;
use App\Models\MenuModel;
use App\Models\RoleMenuRelationModel;
use App\Models\RoleModel;
use App\Models\AdminModel;
use App\Service\common\RedisService;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class AdminService extends BaseService
{
public function __construct()
{
parent::__construct();
$this->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;
}
}