From bd26d52ffffdb0462768f661ac1769c53f78c1b2 Mon Sep 17 00:00:00 2001 From: lq Date: Mon, 5 May 2025 14:20:17 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9B=B8=E5=85=B3api?= =?UTF-8?q?=E3=80=81jwt=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/BaseApp/BaseController.php | 4 +- app/BaseApp/BaseService.php | 270 ++++++++++++++++++++++-- app/Enum/StatusEnum.php | 20 ++ app/Enum/UserStatusEnum.php | 23 ++ app/Http/Controllers/UserController.php | 46 ++++ app/Service/LoginService.php | 22 +- app/Service/UserService.php | 143 +++++++++++++ app/Service/common/JWTService.php | 4 +- config/cc.php | 135 ++++++++++++ routes/api.php | 6 + routes/web.php | 14 +- 11 files changed, 660 insertions(+), 27 deletions(-) create mode 100755 app/Enum/StatusEnum.php create mode 100755 app/Enum/UserStatusEnum.php create mode 100755 config/cc.php diff --git a/app/BaseApp/BaseController.php b/app/BaseApp/BaseController.php index 7b7ea85e..b0d20feb 100755 --- a/app/BaseApp/BaseController.php +++ b/app/BaseApp/BaseController.php @@ -147,7 +147,9 @@ class BaseController if (mb_strlen(strval($checkValue)) <= 0 && !in_array($fieldName, $this->notRequest)) { $requiredFields[] = $fieldName; } else { - $result[$fieldName] = $checkValue; + if (!empty($checkValue)) { + $result[$fieldName] = $checkValue; + } } } diff --git a/app/BaseApp/BaseService.php b/app/BaseApp/BaseService.php index 8fbcd56b..52aa52bd 100755 --- a/app/BaseApp/BaseService.php +++ b/app/BaseApp/BaseService.php @@ -3,41 +3,94 @@ namespace App\BaseApp; use App\Models\UserModel; +use App\Service\common\JWTService; use App\Service\common\UtilsService; +use Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class BaseService { - /** * @var mixed 单例实例,确保该类只有一个全局实例 */ - private static mixed $_instance; + protected static mixed $_instance; + /** * @var object 工具类对象,用于提供通用的功能方法 */ protected object $utils; + /** * @var int 用户ID,默认值为0,表示未登录状态 */ protected int $userId = 0; + /** * @var array 用户信息 */ protected array $userInfo = []; + /** * @var object 数据模型对象,用于数据库操作 */ protected $model; - protected $isAuth = true; + /** + * @var bool 是否需要验证用户权限 + */ + protected bool $isAuth = true; + + /** + * @var array 查询的字段 + */ + protected array $selectField = ['*']; + + /** + * @var array 查询条件Between + */ + protected array $whereBetween = []; + + /** + * @var array 排序条件 + */ + protected array $orderBy = [ + 'name' => 'id', + 'sort' => 'desc', + ]; + + /** + * @var array 查询条件 + */ + protected array $where = [ + ['deleted_at', '=', 0] + ]; + + /** + * @var array 查询条件In + */ + protected array $whereIn = []; + + /** + * @var array 下拉列表字段 + */ + protected array $optionField = ['id', 'name']; + + /** + * @var array 关联加载 + */ + protected array $with = []; + + /** + * @var string 时间字段 + */ + protected string $timeField = 'created_at'; public function __construct() { if ($this->isAuth) { - // 如果是home的接口可以不执行这个方法 - if (!\request()->is('api/home/*')) { - $this->authUser(); - } + $this->userInfo = JWTService::getInstance()->getToken()->getUserInfo(); + $this->userId = $this->userInfo['id'] ?? 0; } $this->utils = UtilsService::getInstance(); } @@ -56,15 +109,202 @@ class BaseService return self::$_instance[$name]; } - private function authUser() + + /** + * 获取列表数据 + * + * 本函数负责根据设定的条件,从数据库中查询并返回分页后的数据列表 + * 它会根据空值条件动态地修改查询,以实现灵活的查询需求 + * + * @return array 返回包含分页信息和数据列表的数组 + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getPageList($isArr = true, $isGetWhere = true): array { - $token = \request()->header('Authorization'); - $token = explode(' ', $token)[1]; - $userModel = UserModel::where('session_key', $token)->first(); - if (!$userModel) { - jexpire('token失效'); + // 初始化查询条件 + if ($isGetWhere === true) $this->getWhere(); + + // 获取范围查询条件 + $searchTime = request()->get('search_time'); + if (!empty($searchTime)) { + $this->getWhereBetween($searchTime); } - $this->userId = $userModel->id; - $this->userInfo = $userModel->toArray(); + + // 执行数据库查询,根据条件动态构建查询语句 + $result = $this->model::when(!empty($this->with), function ($query) { + // 如果关联加载条件存在,则添加关联加载 + $query->with($this->with); + }) + ->when(!empty($this->where), function ($query) { + // 如果查询条件存在,则添加查询条件 + $query->where($this->where); + }) + ->when(!empty($this->whereIn), function ($query) { + // 如果查询条件存在,则添加查询条件 + $query->whereIn($this->whereIn[0], $this->whereIn[1]); + }) + ->when(!empty($this->whereBetween), function ($query) { + // 如果范围查询条件存在,则添加范围查询 + $query->whereBetween($this->whereBetween[0], $this->whereBetween[1]); + }) + ->when(!empty($this->orderBy), function ($query) { + // 如果排序条件存在,则添加排序条件 + $query->orderBy($this->orderBy['name'], $this->orderBy['sort']); + }) + ->select($this->selectField) + // 执行分页查询,每页返回10条记录 + ->paginate(request()->get('pageSize', 20)); + + if ($isArr === true) { + $result = $result->toArray() ?? []; + // 返回分页信息和数据列表 + return [ + 'page' => $result['current_page'], + 'size' => $result['per_page'], + 'page_count' => $result['last_page'], + 'total' => $result['total'], + 'items' => $result['data'], + ]; + } + + // 返回分页信息和数据列表 + return [ + 'page' => $result->currentPage(), + 'size' => $result->perPage(), + 'page_count' => $result->lastPage(), + 'total' => $result->total(), + 'items' => $result->items(), + ]; + } + + /** + * 获取下拉列表数据 + * @return mixed + */ + public function getOption(): mixed + { + $this->getWhere(); + return $this->model::when(!empty($this->where), function ($query) { + $query->where($this->where); + })->when(!empty($this->whereIn), function ($query) { + $query->where($this->whereIn[0], $this->whereIn[1]); + })->orderBy($this->orderBy['name'], $this->orderBy['sort'])->get($this->optionField); + } + + /** + * 详情 + * + * @param $id + * @return mixed + * @throws Exception + */ + public function getDetail($id): mixed + { + $info = $this->model::with($this->with)->select($this->selectField)->find($id); + if (empty($info)) { + return $this->utils->notFound('数据不存在'); + } + return $info; + } + + /** + * 新增 + * @param $params + * @return mixed + */ + public function insert($params): mixed + { + if (!array_key_exists('created_at', $params)) { + $params['created_at'] = time(); + } +// return $this->model::insertGetId($this->utils->truncatedOss($params)); + return $this->model::insertGetId($params); + } + + /** + * 编辑 + * @param $id + * @param $params + * @return mixed + * @throws Exception + */ + public function save($id, $params): mixed + { + if (empty(trim($id))) { + $this->utils->errorThrow('参数错误'); + } + $this->where[] = ['id', '=', $id]; + // 软删除 + $info = $this->model::where($this->where)->first(); + if (empty($info)) { + return $this->utils->notFound('数据不存在'); + } + if (!array_key_exists('updated_at', $params)) { + $params['updated_at'] = time(); + } +// return $this->model::where('id', $id)->update($this->utils->truncatedOss($params)); + return $this->model::where('id', $id)->update($params); + } + + /** + * 删除 + * + * @param $id + * @return mixed|true + * @throws Exception + */ + public function del($id): mixed + { + // 软删除 + $info = $this->model::whereIn('id', $id)->get(['id']); + if (empty($info)) { + return $this->utils->notFound('数据不存在'); + } + if ($this->model::whereIn('id', $id)->update([ + 'deleted_at' => time(), + 'updated_at' => time() + ])) { + return true; + } else { + $this->utils->errorThrow('删除失败'); + } + } + + /** + * 获取查询条件 + * @return void + */ + protected function getWhere(): void + { + $query = request()->query(); + if (!empty($this->queryField)) { + foreach ($this->queryField as $key => $value) { + if (in_array($key, array_keys($query))) { + if (!empty($query[$key] ?? '') || $query[$key] == '0') { + switch ($value) { + case '=': + $this->where[] = [$key, $value, $query[$key]]; + break; + case 'like': + $this->where[] = [$key, $value, '%' . $query[$key] . '%']; + break; + default: + break; + } + } + } + } + } + } + + /** + * 获取时间范围查询条件 + * @param $searchTime + * @return void + */ + protected function getWhereBetween($searchTime): void + { + $this->whereBetween = [$this->timeField, [strtotime($searchTime[0] ?? date('Y-m-01 00:00:00')), strtotime(date('Y-m-d 23:59:59', strtotime($searchTime[1])) ?? date('Y-m-d 23:59:59'))]]; } } diff --git a/app/Enum/StatusEnum.php b/app/Enum/StatusEnum.php new file mode 100755 index 00000000..aca72939 --- /dev/null +++ b/app/Enum/StatusEnum.php @@ -0,0 +1,20 @@ + '正常', + self::DISABLE => '禁用', + }; + } +} diff --git a/app/Enum/UserStatusEnum.php b/app/Enum/UserStatusEnum.php new file mode 100755 index 00000000..aa0583bd --- /dev/null +++ b/app/Enum/UserStatusEnum.php @@ -0,0 +1,23 @@ + '正常', + self::DISABLE => '冻结', + self::DELETED => '注销', + default => '未知', + }; + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index f2ea61ee..70080246 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -3,8 +3,54 @@ namespace App\Http\Controllers; use App\BaseApp\BaseController; +use App\Service\UserService; +use Exception; +use Illuminate\Http\JsonResponse; class UserController extends BaseController { // + + public function __construct() + { + parent::__construct(); + $this->service = UserService::getInstance(); + $this->insertField = [ 'account', 'nick_name', 'password', 'role_id' ]; + $this->updateField = [ 'id', 'account', 'nick_name', 'role_id' ]; + $this->notRequest = ['qr_code', 'avatar', 'email', 'status']; + } + + /** + * 修改密码 + * @Method POST + * @return JsonResponse + * @throws Exception + */ + public function changePassword(): JsonResponse + { + $this->insertField = ['password', 'new_password']; + $params = $this->checkRequiredFields(request()->post()); + + return jok( + $this->service->changePassword($params['password'], $params['new_password']), + '重置密码成功' + ); + } + + /** + * 重置密码 + * @Method POST + * @return JsonResponse + * @throws Exception + */ + public function resetPassword(): JsonResponse + { + $this->insertField = ['id', 'password', 'new_password']; + $params = $this->checkRequiredFields(request()->post()); + + return jok( + $this->service->resetPassword($params['id'], $params['password'], $params['new_password']), + '重置密码成功' + ); + } } diff --git a/app/Service/LoginService.php b/app/Service/LoginService.php index 1dd674fb..dba71d8a 100644 --- a/app/Service/LoginService.php +++ b/app/Service/LoginService.php @@ -5,6 +5,7 @@ namespace App\Service; use App\Models\UserModel; use App\Service\common\JWTService; use App\Service\common\UtilsService; +use Exception; use Illuminate\Support\Str; class LoginService @@ -29,11 +30,19 @@ class LoginService return self::$_instance[$name]; } + /** + * 登录 + * @param $account + * @param $password + * @return array + * @throws Exception + */ public function login($account, $password) { $userModel = UserModel::where('account', $account)->first(); if (empty($userModel)) { - return $this->register($account, $password); + UtilsService::getInstance()->errorThrow('用户或密码错误!'); +// return $this->register($account, $password); } if (!password_verify($password, $userModel->password)) { @@ -62,6 +71,7 @@ class LoginService 'account' => $userModel->account, 'nick_name' => $userModel->nick_name, 'avatar' => $userModel->avatar, + 'email' => $userModel->email, 'ip' => $userModel->ip, 'ip_table' => $userModel->ip_table, ]; @@ -70,7 +80,14 @@ class LoginService return $result; } - public function register($account, $password) + /** + * 注册 + * @param $account + * @param $password + * @return array + * @throws Exception + */ + public function register($account, $password): array { $userModel = UserModel::where('account', $account)->first(); if ($userModel) { @@ -96,6 +113,7 @@ class LoginService 'account' => $createModel->account, 'nick_name' => $createModel->nick_name, 'avatar' => $createModel->avatar, + 'email' => $userModel->email?? '', 'ip' => $createModel->ip, 'ip_table' => $createModel->ip_table, ]; diff --git a/app/Service/UserService.php b/app/Service/UserService.php index a4475ae8..74cc513b 100644 --- a/app/Service/UserService.php +++ b/app/Service/UserService.php @@ -3,8 +3,151 @@ namespace App\Service; use App\BaseApp\BaseService; +use App\Enum\UserStatusEnum; +use App\Models\UserModel; +use Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class UserService extends BaseService { + public function __construct() + { + parent::__construct(); + $this->selectField = [ 'id', 'account', 'nick_name', 'avatar', 'email', 'balance', 'qr_code', 'role_id', 'ip', 'ip_table', 'status', 'last_reset_password_at', 'created_at', 'updated_at', 'deleted_at', ]; + $this->model = UserModel::class; + } + /** + * 获取用户列表 + * @return array + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function list(): array + { + $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('account', $params['account'])->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 $id + * @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), + 'last_reset_password_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), + 'last_reset_password_at' => get_time() + ]); + if (!$result) { + return $this->utils->errorThrow('重置密码失败'); + } + return $result; + } } diff --git a/app/Service/common/JWTService.php b/app/Service/common/JWTService.php index c78f527b..6432c66d 100755 --- a/app/Service/common/JWTService.php +++ b/app/Service/common/JWTService.php @@ -48,7 +48,7 @@ class JWTService public function generateToken(array $data): string { $issuedAt = time(); - $expirationTime = $issuedAt + config('xk.redis.jwt_ttl'); + $expirationTime = $issuedAt + config('cc.redis.jwt_ttl', 600000); $payload = [ 'iat' => $issuedAt, @@ -84,7 +84,7 @@ class JWTService { try { $jwt = $this->parseToken(); - $user = RedisService::getInstance()->init(config('xk.redis.jwt'))->get($jwt->data->id); + $user = RedisService::getInstance()->init(config('cc.redis.jwt'))->get($jwt->data->id); if (empty($user)) return UtilsService::getInstance()->notAuth('登录状态过期'); return json_decode($user, true); } catch (Exception $e) { diff --git a/config/cc.php b/config/cc.php new file mode 100755 index 00000000..86288e45 --- /dev/null +++ b/config/cc.php @@ -0,0 +1,135 @@ + [ + // 订单 + 'product_order' => [ + 'over_time' => 24*3600,//秒,自动取消时间 +// 'over_time' => 3,//秒,自动取消时间 + 'received_time' => 7*24*3600,//自动确认收货时间 + 'forbidden_refund_time' => 30*24*3600, //不允许退款时间 + 'settlement_time' => 0//7*24*3600//订单发货 分账自动结算时间 + ], + // 处方 + 'prescription' => [ + 'over_time' => 24*3600 //处方审核自动失效时间 +// 'over_time' => 0 //处方审核自动失效时间 + ], + ], + /* + * redis配置 + */ + 'redis' => [ + 'jwt' => 'xk_login_', + 'jwt_ttl' => 360000, +// 'jwt_ttl' => 360, + 'email' => 'xk_email_', + 'phone' => 'xk_phone_', + 'login_out_key' => 'xk_login_out_', + 'menu_key' => 'xk_menu_', + ], + /* + * api白名单 + */ + 'api' => [ + 'white_list' => [ + '/api/admin/login', + '/api/admin/register', + ] + ], + /* + * 阿里云oss配置 + */ + 'oss' => [ + 'truncated_list' => [ + 'avatar', + 'logo', + 'url', + 'image_url', + ], + 'redis_key' => 'xk_oss_url_', + 'ali' => [ + 'accessKeyId' => env('OSS_ACCESS_KEY_ID'), + 'accessKeySecret' => env('OSS_ACCESS_KEY_SECRET'), + 'endpoint' => env('OSS_ENDPOINT'), + 'bucket' => env('OSS_BUCKET'), + 'domain' => env('OSS_DOMAIN'), + ] + ], + /* + * 短信配置 + */ + 'sms' => [ + 'ali' => [ + 'status' => '1', + 'access_key_id' => env('ALIYUN_SMS_ACCESS_KEY_ID'), + 'access_key_secret' => env('ALIYUN_SMS_ACCESS_KEY_SECRET'), + 'sign_name' => env('ALIYUN_SMS_SIGN_NAME'), + 'captcha' => [ + "template_id" => "SMS_275060336", // 模板id + "template_variable" => "code", //模板变量 + ], + "doctor" => [ + "register" => [ //挂号订单提醒 + "template_id" => "SMS_462205458", + "template_variable" => "" + ], + "prescription_pass" => [ + "template_id" => "SMS_462260412", + "template_variable" => "order" + ], + "prescription_refuse" => [ + "template_id" => "SMS_462230433", + "template_variable" => [ + "order", + "cause" + ] + ] + ], + "pharmacist" => [ + "wait_approval" => [ + "template_id" => "SMS_462255406", + "template_variable" => "" + ] + ] + ] + ], + /* + * 旧接口配置 + */ + 'base_url' => [ + 'old_api' => env('BASE_OLD_API_URL', ''), + 'old_shop_api' => env('BASE_OLD_SHOP_API_URL', ''), + // 微信接口 + 'we_chat_api' => env('BASE_WE_CHAT_API_URL', ''), + ], + /* + * 日志配置 + */ + 'log' => [ + // 不添加返回记录的api接口 + 'no_insert' => [ + 'api/admin/auth/login', + 'api/admin/auth/send-verification-code', + 'api/admin/auth/my-info', + 'api/admin/auth/codes', + 'api/admin/auth/menu', + 'api/admin/log/api-list', + 'api/admin/log/ledger-list', + 'api/admin/log/op-list', + 'api/admin/log/prescription-list', + ] + ], + 'app' => [ + 'we_chat' => [ + 'app_id' => '', + 'app_secret' => '', + 'merchant_id' => '', + 'merchant_key' => '', + 'apiclient_cert' => '', + 'apiclient_key' => '' + ] + ], +]; diff --git a/routes/api.php b/routes/api.php index a40a8e10..d6cd98bd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -13,3 +13,9 @@ Route::get('/', function () { UtilsService::class::getInstance()->autoRouteRegister([ '' => \App\Http\Controllers\LoginController::class, // 登录控制器 ]); + +Route::group([], function () { + UtilsService::class::getInstance()->autoRouteRegister([ + 'user' => \App\Http\Controllers\UserController::class, // 用户控制器 + ]); +}); diff --git a/routes/web.php b/routes/web.php index 96708a99..f35ab5bb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,12 +3,12 @@ use Illuminate\Support\Facades\Route; Route::get('/', function () { - - $framework = \App\Models\ProjectModel::with([ - 'frameworks', - 'labels', - 'technologyStacks', - ])->get(); - ds($framework); +// +// $framework = \App\Models\ProjectModel::with([ +// 'frameworks', +// 'labels', +// 'technologyStacks', +// ])->get(); +// ds($framework); return view('welcome'); });