From b90cf18d06def2fa0675fc8f8b433f0a8be45db7 Mon Sep 17 00:00:00 2001 From: jsasg <735273025@qq.com> Date: Sat, 4 Jan 2025 18:09:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=A2=9E/=E5=88=A0/=E6=94=B9/=E6=9F=A5?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local | 5 + app/admin/config/apiret.php | 22 ++++ app/admin/config/auth.php | 9 ++ app/admin/controller/v1/Article.php | 9 ++ app/admin/controller/v1/ArticleCategory.php | 116 ++++++++++++++++++ app/admin/middleware.php | 3 +- app/admin/middleware/v1/Auth.php | 77 ++++++++++++ app/admin/model/v1/ArticleCategoryModel.php | 50 ++++++++ app/admin/model/v1/ArticleModel.php | 43 +++++++ app/admin/model/v1/CountryModel.php | 30 +++++ app/admin/model/v1/LanguageModel.php | 39 ++++++ app/admin/model/v1/UserLoginLogModel.php | 6 +- app/admin/route/v1.php | 21 ++++ .../validate/v1/ArticleCategoryValidate.php | 46 +++++++ config/jwt.php | 2 +- 15 files changed, 473 insertions(+), 5 deletions(-) create mode 100644 app/admin/config/apiret.php create mode 100644 app/admin/config/auth.php create mode 100644 app/admin/controller/v1/Article.php create mode 100644 app/admin/controller/v1/ArticleCategory.php create mode 100644 app/admin/middleware/v1/Auth.php create mode 100644 app/admin/model/v1/ArticleCategoryModel.php create mode 100644 app/admin/model/v1/ArticleModel.php create mode 100644 app/admin/model/v1/CountryModel.php create mode 100644 app/admin/model/v1/LanguageModel.php create mode 100644 app/admin/validate/v1/ArticleCategoryValidate.php diff --git a/.env.local b/.env.local index 042786a0..3d871698 100644 --- a/.env.local +++ b/.env.local @@ -13,3 +13,8 @@ DEFAULT_LANG = zh-cn [JWT] SECRET=b43e6276644ed60e65c50d1b324ba10b + +# 后台不需要登录的接口 +[ADMIN_AUTH] +WHITE_LIST[] = v1/user/login +WHITE_LIST[] = v1/user/captcha diff --git a/app/admin/config/apiret.php b/app/admin/config/apiret.php new file mode 100644 index 00000000..a420f5a2 --- /dev/null +++ b/app/admin/config/apiret.php @@ -0,0 +1,22 @@ + 'code', + 'message_var' => 'msg', + 'data_var' => 'data', + 'states' => [ + 'success' => [ + 'code' => 0, + 'msg' => '操作成功!', + 'data' => [] + ], + 'error' => [ + 'code' => 1, + 'msg' => '操作错误!', + 'data' => [] + ], + ] +]; diff --git a/app/admin/config/auth.php b/app/admin/config/auth.php new file mode 100644 index 00000000..799fb30e --- /dev/null +++ b/app/admin/config/auth.php @@ -0,0 +1,9 @@ + env('ADMIN_AUTH.WHITE_LIST', ['v1/user/login','1/user/captcha']), +]; \ No newline at end of file diff --git a/app/admin/controller/v1/Article.php b/app/admin/controller/v1/Article.php new file mode 100644 index 00000000..34c6c686 --- /dev/null +++ b/app/admin/controller/v1/Article.php @@ -0,0 +1,9 @@ +param([ + 'name', + 'page/d' => 1, + 'limit/d' => 10, + ]); + + $category = ArticleCategoryModel::withoutField([ + 'language_id', + 'deleted_at', + 'seo_title', + 'seo_keywords', + 'seo_desc', + ]) + ->withSearch(['name'], ['name' => $param['name']]) + ->order('sort', 'asc') + ->page($param['page'], $param['limit']) + ->select(); + + return success('获取成功', $category); + } + + // 分类详情 + public function read() + { + $id = request()->param('id'); + $category = ArticleCategoryModel::where('id', '=', $id) + ->withoutField(['language_id', 'deleted_at']) + ->find(); + if (is_null($category)) { + return error('文章分类不存在'); + } + return success('获取成功', $category); + } + + // 添加分类 + public function save() + { + $post = request()->post([ + 'name', + 'sort' => 0, + 'is_show' => 1, + 'seo_title', + 'seo_keywords', + 'seo_desc', + ]); + + $data = array_merge($post, ['language_id' => request()->lang_id]); + $valiate = new ArticleCategoryValidate; + if (!$valiate->check($data)) { + return error($valiate->getError()); + } + + $category = new ArticleCategoryModel(); + if (!$category->save($data)) { + return error('操作失败'); + } + return success('操作成功'); + } + + // 更新分类 + public function update() + { + $id = request()->param('id'); + $put = request()->put([ + 'name', + 'sort', + 'is_show', + 'seo_title', + 'seo_keywords', + 'seo_desc', + ]); + $data = array_merge($put, ['language_id' => request()->lang_id]); + + $valiate = new ArticleCategoryValidate; + if (!$valiate->check(array_merge($data, ['id' => $id]))) { + return error($valiate->getError()); + } + + $category = ArticleCategoryModel::where('id', '=', $id)->find(); + if (is_null($category)) { + return error('请确认操作对象是否存在'); + } + if (!$category->save($data)) { + return error('操作失败'); + } + + return success('操作成功'); + } + + // 删除分类 + public function delete() + { + $id = request()->param('id'); + $category = ArticleCategoryModel::where('id', '=', $id)->find(); + if (is_null($category)) { + return error('请确认操作对象是否存在'); + } + if (!$category->useSoftDelete('deleted_at', date('Y-m-d H:m:s', time()))->delete()) { + return error('操作失败'); + } + return success('操作成功'); + } +} diff --git a/app/admin/middleware.php b/app/admin/middleware.php index ec9473e0..cb6a4a9b 100644 --- a/app/admin/middleware.php +++ b/app/admin/middleware.php @@ -1,5 +1,6 @@ cookie('lang', 'zh_cn'); + $lang = LanguageModel::cache('lang:code.' . $code, 3600, 'lang') + ->field(['id', 'name', 'code', 'icon', 'url']) + ->withJoin(['country' => ['id', 'name', 'code', 'icon']]) + ->where('language_model.code', '=', $code) + ->find(); + if (!is_null($lang)) { + $request->lang_id = $lang['id']; + $request->country_id = $lang->country['id']; + } + } + + /** + * 处理请求 + * + * @param \think\Request $request + * @param \Closure $next + * @return Response + */ + public function handle($request, \Closure $next) + { + // options 请求过滤 + if ($request->isOptions()) { + return response(); + } + + // 白名单 + if (in_array($request->pathinfo(), config('auth.white_list'))) { + return $next($request); + } + + try { + // 验证 token + $payload = $this->auth->auth(); + + // 将用户信息存入请求 + $request->uid = $payload['uid']; + + // 语言信息 + $this->setLanguage($request); + } catch (\thans\jwt\exception\TokenExpiredException $e) { + try { + // 尝试刷新 token + $this->auth->setRefresh(); + + // 刷新 token + $token = $this->auth->refresh(); + + $payload = $this->auth->auth(false); + $request->uid = $payload['uid']; + + // 语言信息 + $this->setLanguage($request); + + $response = $next($request); + return $this->setAuthentication($response, $token); + } catch (\thans\jwt\exception\TokenBlacklistGracePeriodException $e) { + return error('登录已过期', [], 401); + } + } catch (\Throwable $th) { + return error('请先登录', [], 401); + } + + return $next($request); + } +} diff --git a/app/admin/model/v1/ArticleCategoryModel.php b/app/admin/model/v1/ArticleCategoryModel.php new file mode 100644 index 00000000..ef138516 --- /dev/null +++ b/app/admin/model/v1/ArticleCategoryModel.php @@ -0,0 +1,50 @@ + 'int', + 'language_id' => 'int', + 'pid' => 'int', + 'name' => 'string', + 'short_name' => 'string', + 'icon' => 'string', + 'desc' => 'string', + 'sort' => 'int', + 'level' => 'int', + 'is_show' => 'int', + 'seo_title' => 'string', + 'seo_keywords' => 'string', + 'seo_desc' => 'string', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; + + // 搜索分类名 + public function searchNameAttr($query, $value, $data) + { + $query->where('name', 'like', '%' . $value . '%'); + } +} diff --git a/app/admin/model/v1/ArticleModel.php b/app/admin/model/v1/ArticleModel.php new file mode 100644 index 00000000..43ca5bdd --- /dev/null +++ b/app/admin/model/v1/ArticleModel.php @@ -0,0 +1,43 @@ + 'int', + 'languge_id' => 'int', + 'category_id' => 'int', + 'title' => 'string', + 'author' => 'string', + 'source' => 'string', + 'image' => 'string', + 'desc' => 'string', + 'recommend' => 'int', + 'release_time' => 'int', + 'sort' => 'int', + 'link' => 'string', + 'content' => 'string', + 'view_count' => 'int', + 'praise_count' => 'int', + 'seo_title' => 'string', + 'seo_keywords' => 'string', + 'seo_desc' => 'string', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + ]; +} diff --git a/app/admin/model/v1/CountryModel.php b/app/admin/model/v1/CountryModel.php new file mode 100644 index 00000000..679fc17a --- /dev/null +++ b/app/admin/model/v1/CountryModel.php @@ -0,0 +1,30 @@ + 'int', + 'name' => 'string', + 'code' => 'string', + 'icon' => 'string', + 'status' => 'int', + 'sort' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; +} diff --git a/app/admin/model/v1/LanguageModel.php b/app/admin/model/v1/LanguageModel.php new file mode 100644 index 00000000..79275719 --- /dev/null +++ b/app/admin/model/v1/LanguageModel.php @@ -0,0 +1,39 @@ + 'int', + 'country_id' => 'int', + 'name' => 'string', + 'code' => 'string', + 'icon' => 'string', + 'url' => 'string', + 'status' => 'int', + 'is_default' => 'int', + 'sort' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + // 关联国家 + public function country() + { + return $this->belongsTo(CountryModel::class, 'country_id', 'id'); + } +} diff --git a/app/admin/model/v1/UserLoginLogModel.php b/app/admin/model/v1/UserLoginLogModel.php index ba90fecd..eaaf2903 100644 --- a/app/admin/model/v1/UserLoginLogModel.php +++ b/app/admin/model/v1/UserLoginLogModel.php @@ -10,13 +10,13 @@ use think\Model; */ class UserLoginLogModel extends Model { - // 设置表名 + // 表名 protected $name = 'sys_user_login_log'; - // 设置主键 + // 主键 protected $pk = 'id'; - // 设置字段信息 + // 字段信息 protected $schema = [ 'id' => 'int', 'user_id' => 'int', diff --git a/app/admin/route/v1.php b/app/admin/route/v1.php index 4a6a94d6..24083568 100644 --- a/app/admin/route/v1.php +++ b/app/admin/route/v1.php @@ -25,6 +25,27 @@ Route::group('v1', function () { // 登录接口 Route::post('login', 'Login/index'); }); + + // 文章模块 + Route::group('article', function () { + // 文章分类 + Route::group('category', function () { + // 分类列表 + Route::get('index', 'ArticleCategory/index'); + + // 分类详情 + Route::get('read/:id', 'ArticleCategory/read'); + + // 分类新增 + Route::post('save', 'ArticleCategory/save'); + + // 分类更新 + Route::put('update/:id', 'ArticleCategory/update'); + + // 分类删除 + Route::delete('delete/:id', 'ArticleCategory/delete'); + }); + }); })->prefix('v1.'); Route::miss(function() { diff --git a/app/admin/validate/v1/ArticleCategoryValidate.php b/app/admin/validate/v1/ArticleCategoryValidate.php new file mode 100644 index 00000000..b334b61f --- /dev/null +++ b/app/admin/validate/v1/ArticleCategoryValidate.php @@ -0,0 +1,46 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = [ + 'language_id' => 'require|integer', + 'name' => 'require|unique:article_category,name^language_id|max:64', + 'sort' => 'require|integer', + 'is_show' => 'require|in:0,1', + 'seo_title' => 'max:255', + 'seo_keywords' => 'max:255', + 'seo_desc' => 'max:255', + ]; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = [ + 'language_id.require' => '语言ID不能为空', + 'language_id.integer' => '语言ID必须为整数', + 'name.require' => '分类名称不能为空', + 'name.unique' => '分类名称已存在', + 'name.max' => '分类名称最多64个字符', + 'sort.require' => '排序不能为空', + 'sort.integer' => '排序必须为整数', + 'is_show.require' => '是否显示不能为空', + 'is_show.in' => '是否显示值必须为0或1', + 'seo_title.max' => 'SEO标题最多255个字符', + 'seo_keywords.max' => 'SEO关键词最多255个字符', + 'seo_desc.max' => 'SEO描述最多255个字符', + ]; +} diff --git a/config/jwt.php b/config/jwt.php index fd94fc3a..986ed651 100644 --- a/config/jwt.php +++ b/config/jwt.php @@ -8,7 +8,7 @@ return [ 'private_key' => env('JWT_PRIVATE_KEY'), 'password' => env('JWT_PASSWORD'), //JWT time to live - 'ttl' => env('JWT_TTL', 60), + 'ttl' => env('JWT_TTL', 3600), //Refresh time to live 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), //JWT hashing algorithm