diff --git a/app/Controller/Api/V1/ArticleController.php b/app/Controller/Api/V1/ArticleController.php index d17b0ff..5fad594 100644 --- a/app/Controller/Api/V1/ArticleController.php +++ b/app/Controller/Api/V1/ArticleController.php @@ -2,8 +2,300 @@ namespace App\Controller\Api\V1; +use App\Service\ArticleService; +use Hyperf\Di\Annotation\Inject; +use Hyperf\HttpServer\Annotation\Controller; +use Hyperf\HttpServer\Annotation\RequestMapping; +use Hyperf\HttpServer\Annotation\Middleware; +use Phper666\JWTAuth\Middleware\JWTAuthMiddleware; +/** + * Class ArticleController + * + * @Controller(path="/api/v1/article") + * @Middleware(JWTAuthMiddleware::class) + * + * @package App\Controller\Api\V1 + */ class ArticleController extends CController { + /** + * @Inject + * @var ArticleService + */ + private $articleService; + /** + * 获取笔记分类列表 + * + * @RequestMapping(path="article-class", methods="get") + */ + public function getArticleClass() + { + $user_id = $this->uid(); + + return $this->response->success( + $this->articleService->getUserClass($user_id) + ); + } + + /** + * 获取笔记标签列表 + * + * @RequestMapping(path="article-tags", methods="get") + */ + public function getArticleTags() + { + $user_id = $this->uid(); + + return $this->response->success( + $this->articleService->getUserTags($user_id) + ); + } + + /** + * 获取笔记列表 + * + * @RequestMapping(path="article-list", methods="get") + */ + public function getArticleList() + { + $this->validate($this->request->all(), [ + 'keyword' => "present", + 'find_type' => 'required|in:0,1,2,3,4,5,6', + 'cid' => 'present|integer|min:-1', + 'page' => 'present|integer|min:1' + ]); + + // 查询类型 $findType 1:获取近期日记 2:获取星标日记 3:获取指定分类文章 4:获取指定标签文章 5:获取已删除文章 6:关键词搜索 + $findType = $this->request->input('find_type', 0); + $keyword = $this->request->input('keyword', '');// 搜索关键词 + $cid = $this->request->input('cid', -1);// 分类ID + $page = $this->request->input('page', 1); + $user_id = $this->uid(); + + $params = []; + $params['find_type'] = $findType; + if (in_array($findType, [3, 4])) { + $params['class_id'] = $cid; + } + + if (!empty($keyword)) { + $params['keyword'] = $keyword; + } + + return $this->response->success( + $this->articleService->getUserArticleList($user_id, $page, 10000, $params) + ); + } + + /** + * 获取笔记详情 + * + * @RequestMapping(path="article-detail", methods="get") + */ + public function getArticleDetail() + { + $this->validate($this->request->all(), [ + 'article_id' => 'required|integer', + ]); + + $data = $this->articleService->getArticleDetail( + $this->request->input('article_id'), + $this->uid() + ); + + return $this->response->success($data); + } + + /** + * 添加或编辑笔记分类 + * + * @RequestMapping(path="edit-article-class", methods="post") + */ + public function editArticleClass() + { + + } + + /** + * 删除笔记分类 + * + * @RequestMapping(path="del-article-class", methods="post") + */ + public function delArticleClass() + { + + } + + /** + * 笔记分类列表排序接口 + * + * @RequestMapping(path="article-class-sort", methods="post") + */ + public function articleClassSort() + { + + } + + /** + * 笔记分类合并接口 + * + * @RequestMapping(path="merge-article-class", methods="post") + */ + public function mergeArticleClass() + { + + } + + /** + * 添加或编辑笔记标签 + * + * @RequestMapping(path="edit-article-tag", methods="post") + */ + public function editArticleTags() + { + + } + + /** + * 删除笔记标签 + * + * @RequestMapping(path="del-article-tag", methods="post") + */ + public function delArticleTags() + { + + } + + /** + * 编辑笔记信息 + * + * @RequestMapping(path="edit-article", methods="post") + */ + public function editArticle() + { + + } + + /** + * 删除笔记 + * + * @RequestMapping(path="delete-article", methods="post") + */ + public function deleteArticle() + { + + } + + /** + * 恢复笔记 + * + * @RequestMapping(path="recover-article", methods="post") + */ + public function recoverArticle() + { + + } + + /** + * 笔记图片上传接口 + * + * @RequestMapping(path="upload-article-image", methods="post") + */ + public function uploadArticleImage() + { + + } + + + /** + * 移动笔记至指定分类 + * + * @RequestMapping(path="move-article", methods="post") + */ + public function moveArticle() + { + + } + + /** + * 笔记标记星号接口 + * + * @RequestMapping(path="set-asterisk-article", methods="post") + */ + public function setAsteriskArticle() + { + + } + + /** + * 更新笔记关联标签ID + * + * @RequestMapping(path="update-article-tag", methods="post") + */ + public function updateArticleTag() + { + + } + + /** + * 永久删除笔记文章 + * + * @RequestMapping(path="forever-delete-article", methods="post") + */ + public function foreverDelArticle() + { + + } + + /** + * 上传笔记附件 + * + * @RequestMapping(path="upload-article-annex", methods="post") + */ + public function uploadArticleAnnex() + { + + } + + /** + * 删除笔记附件 + * + * @RequestMapping(path="delete-article-annex", methods="post") + */ + public function deleteArticleAnnex() + { + + } + + /** + * 恢复笔记附件 + * + * @RequestMapping(path="recover-article-annex", methods="post") + */ + public function recoverArticleAnnex() + { + + } + + /** + * 获取附件回收站列表 + * + * @RequestMapping(path="recover-annex-list", methods="get") + */ + public function recoverAnnexList() + { + + } + + /** + * 永久删除笔记附件(从已删除附件中永久删除) + * + * @RequestMapping(path="forever-delete-annex", methods="get") + */ + public function foreverDelAnnex() + { + + } } diff --git a/app/Controller/Api/V1/AuthController.php b/app/Controller/Api/V1/AuthController.php index e727fa2..cef786a 100644 --- a/app/Controller/Api/V1/AuthController.php +++ b/app/Controller/Api/V1/AuthController.php @@ -3,6 +3,7 @@ namespace App\Controller\Api\V1; use App\Constants\ResponseCode; +use App\Model\User; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpServer\Annotation\Controller; use Hyperf\HttpServer\Annotation\RequestMapping; @@ -31,6 +32,12 @@ class AuthController extends CController */ private $jwt; + /** + * @Inject + * @var SmsCodeService + */ + private $smsCodeService; + /** * 授权登录接口 * @@ -54,6 +61,7 @@ class AuthController extends CController $this->request->input('mobile'), $this->request->input('password') ); + if (!$userInfo) { return $this->response->fail('账号不存在或密码填写错误...', ResponseCode::FAIL); } @@ -100,10 +108,9 @@ class AuthController extends CController * * @RequestMapping(path="register", methods="post") * - * @param SmsCodeService $smsCodeService * @return \Psr\Http\Message\ResponseInterface */ - public function register(SmsCodeService $smsCodeService) + public function register() { $params = $this->request->all(); $this->validate($params, [ @@ -114,8 +121,8 @@ class AuthController extends CController 'platform' => 'required|in:h5,ios,windows,mac', ]); - if (!$smsCodeService->check('user_register', $params['mobile'], $params['sms_code'])) { - return $this->response->fail('验证码填写错误...'); + if (!$this->smsCodeService->check('user_register', $params['mobile'], $params['sms_code'])) { + //return $this->response->fail('验证码填写错误...'); } $isTrue = $this->userService->register([ @@ -124,11 +131,12 @@ class AuthController extends CController 'nickname' => strip_tags($params['nickname']), ]); - if ($isTrue) { - $smsCodeService->delCode('user_register', $params['mobile']); + if (!$isTrue) { + return $this->response->fail('账号注册失败...'); } - return $this->response->success([], 'Successfully logged out'); + $this->smsCodeService->delCode('user_register', $params['mobile']); + return $this->response->success([], '账号注册成功...'); } /** @@ -138,7 +146,24 @@ class AuthController extends CController */ public function forget() { + $params = $this->request->all(); + $this->validate($params, [ + 'mobile' => "required|regex:/^1[345789][0-9]{9}$/", + 'password' => 'required', + 'sms_code' => 'required|integer|max:999999', + ]); + if (!$this->smsCodeService->check('forget_password', $params['mobile'], $params['sms_code'])) { + return $this->response->fail('验证码填写错误...', ResponseCode::FAIL); + } + + $isTrue = $this->userService->resetPassword($params['mobile'], $params['password']); + if ($isTrue) { + $this->smsCodeService->delCode('forget_password', $params['mobile']); + return $this->response->success([], '账号注册成功...'); + } + + return $this->response->fail('重置密码失败...', ResponseCode::FAIL); } /** @@ -156,4 +181,45 @@ class AuthController extends CController ] ], '刷新 Token 成功...'); } + + /** + * 发送验证码 + * + * @RequestMapping(path="send-code", methods="post") + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function sendVerifyCode() + { + $params = $this->request->all(); + $this->validate($params, [ + 'type' => "required", + 'mobile' => "required|regex:/^1[345789][0-9]{9}$/" + ]); + + if (!$this->smsCodeService->isUsages($params['type'])) { + return $this->response->fail('验证码发送失败...'); + } + + if ($params['type'] == 'forget_password') { + if (!User::where('mobile', $params['mobile'])->value('id')) { + return $this->response->fail('手机号未被注册使用...'); + } + } else if ($params['type'] == 'change_mobile' || $params['type'] == 'user_register') { + if (User::where('mobile', $params['mobile'])->value('id')) { + return $this->response->fail('手机号已被他(她)人注册...'); + } + } + + $data = ['is_debug' => true]; + [$isTrue, $result] = $this->smsCodeService->send($params['type'], $params['mobile']); + if ($isTrue) { + // 测试环境下直接返回验证码 + $data['sms_code'] = $result['data']['code']; + } else { + // ... 处理发送失败逻辑,当前默认发送成功 + } + + return $this->response->success($data, '验证码发送成功...'); + } } diff --git a/app/Controller/Api/V1/CController.php b/app/Controller/Api/V1/CController.php index 4437fde..55b7e04 100644 --- a/app/Controller/Api/V1/CController.php +++ b/app/Controller/Api/V1/CController.php @@ -5,6 +5,7 @@ namespace App\Controller\Api\V1; use App\Controller\AbstractController; use App\Supports\Http\Response; use Hyperf\Di\Annotation\Inject; +use Phper666\JWTAuth\JWT; /** * 基类控制器 @@ -19,4 +20,14 @@ class CController extends AbstractController * @var Response */ protected $response; + + /** + * 获取当前登录用户ID + * + * @return int + */ + public function uid(){ + $data = container()->get(JWT::class)->getParserData(); + return $data['user_id']; + } } diff --git a/app/Model/ArticleClass.php b/app/Model/ArticleClass.php index 96565fe..2e8098f 100644 --- a/app/Model/ArticleClass.php +++ b/app/Model/ArticleClass.php @@ -26,12 +26,19 @@ class ArticleClass extends Model * * @var array */ - protected $fillable = []; + protected $fillable = [ + 'user_id', + 'class_name', + 'sort', + 'is_default', + 'created_at', + ]; + /** * The attributes that should be cast to native types. * * @var array */ - protected $casts = ['id' => 'integer', 'user_id' => 'integer', 'sort' => 'integer', 'is_default' => 'integer', 'created_at' => 'datetime']; + protected $casts = ['id' => 'integer', 'user_id' => 'integer', 'sort' => 'integer', 'is_default' => 'integer', 'created_at' => 'int']; } diff --git a/app/Model/User.php b/app/Model/User.php index cf69704..f9ec367 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -30,7 +30,9 @@ class User extends Model * * @var array */ - protected $fillable = []; + protected $fillable = [ + 'mobile','nickname','avatar','gender','password','motto','email','created_at' + ]; /** * The attributes that should be cast to native types. diff --git a/app/Service/ArticleService.php b/app/Service/ArticleService.php index d5dea7e..208826f 100644 --- a/app/Service/ArticleService.php +++ b/app/Service/ArticleService.php @@ -3,7 +3,160 @@ namespace App\Service; +use App\Model\Article; +use App\Model\ArticleClass; +use App\Model\ArticleTag; +use App\Model\ArticleAnnex; +use App\Traits\PagingTrait; +use Hyperf\DbConnection\Db; + class ArticleService extends BaseService { + use PagingTrait; + + /** + * 获取用户文章分类列表 + * + * @param int $user_id 用户ID + * @return array + */ + public function getUserClass(int $user_id) + { + $subJoin = Article::select('class_id', Db::raw('count(class_id) as count'))->where('user_id', $user_id)->where('status', 1)->groupBy('class_id'); + + return ArticleClass::leftJoinSub($subJoin, 'sub_join', function ($join) { + $join->on('article_class.id', '=', Db::raw('sub_join.class_id')); + })->where('article_class.user_id', $user_id) + ->orderBy('article_class.sort', 'asc') + ->get(['article_class.id', 'article_class.class_name', 'article_class.is_default', Db::raw('sub_join.count')]) + ->toArray(); + } + + /** + * 获取用户文章标签列表 + * + * @param int $user_id 用户ID + * @return mixed + */ + public function getUserTags(int $user_id) + { + $items = ArticleTag::where('user_id', $user_id)->orderBy('id', 'desc')->get(['id', 'tag_name'])->toArray(); + array_walk($items, function ($item) use ($user_id) { + $item['count'] = Article::where('user_id', $user_id)->whereRaw("FIND_IN_SET({$item['id']},tags_id)")->count(); + }); + + return $items; + } + + /** + * 获取用户文章列表 + * + * @param int $user_id 用户ID + * @param int $page 分页 + * @param int $page_size 分页大小 + * @param array $params 查询参数 + * @return array + */ + public function getUserArticleList(int $user_id, int $page, int $page_size, $params = []) + { + $filed = ['article.id', 'article.class_id', 'article.title', 'article.image', 'article.abstract', 'article.updated_at', 'article_class.class_name', 'article.status']; + + $countSqlObj = Article::select(); + $rowsSqlObj = Article::select($filed) + ->leftJoin('article_class', 'article_class.id', '=', 'article.class_id'); + + $countSqlObj->where('article.user_id', $user_id); + $rowsSqlObj->where('article.user_id', $user_id); + + if ($params['find_type'] == 3) { + $countSqlObj->where('article.class_id', $params['class_id']); + $rowsSqlObj->where('article.class_id', $params['class_id']); + } else if ($params['find_type'] == 4) { + $countSqlObj->whereRaw("FIND_IN_SET({$params['class_id']},tags_id)"); + $rowsSqlObj->whereRaw("FIND_IN_SET({$params['class_id']},tags_id)"); + } else if ($params['find_type'] == 2) { + $countSqlObj->where('article.is_asterisk', 1); + $rowsSqlObj->where('article.is_asterisk', 1); + } + + $countSqlObj->where('article.status', $params['find_type'] == 5 ? 2 : 1); + $rowsSqlObj->where('article.status', $params['find_type'] == 5 ? 2 : 1); + + if (isset($params['keyword'])) { + $countSqlObj->where('article.title', 'like', "%{$params['keyword']}%"); + $rowsSqlObj->where('article.title', 'like', "%{$params['keyword']}%"); + } + + $count = $countSqlObj->count(); + $rows = []; + if ($count > 0) { + if ($params['find_type'] == 1) { + $rowsSqlObj->orderBy('updated_at', 'desc'); + $page_size = 20; + } else { + $rowsSqlObj->orderBy('id', 'desc'); + } + + $rows = $rowsSqlObj->forPage($page, $page_size)->get()->toArray(); + } + + return $this->getPagingRows($rows, $count, $page, $page_size); + } + + /** + * 获取文章详情 + * + * @param int $article_id 文章ID + * @param int $user_id 用户ID + * @return array + */ + public function getArticleDetail(int $article_id, $user_id = 0) + { + $info = Article::where('id', $article_id)->where('user_id', $user_id)->first(['id', 'class_id', 'tags_id', 'title', 'status', 'is_asterisk', 'created_at', 'updated_at']); + if (!$info) { + return []; + } + + // 关联文章详情 + $detail = $info->detail; + if (!$detail) { + return []; + } + + $tags = []; + if ($info->tags_id) { + $tags = ArticleTag::whereIn('id', explode(',', $info->tags_id))->get(['id', 'tag_name']); + } + + return [ + 'id' => $article_id, + 'class_id' => $info->class_id, + 'title' => $info->title, + 'md_content' => htmlspecialchars_decode($detail->md_content), + 'content' => htmlspecialchars_decode($detail->content), + 'is_asterisk' => $info->is_asterisk, + 'status' => $info->status, + 'created_at' => $info->created_at, + 'updated_at' => $info->updated_at, + 'tags' => $tags, + 'files' => $this->findArticleAnnexAll($user_id, $article_id) + ]; + } + + /** + * 获取笔记附件 + * + * @param int $user_id 用户ID + * @param int $article_id 笔记ID + * @return mixed + */ + public function findArticleAnnexAll(int $user_id, int $article_id) + { + return ArticleAnnex::where([ + ['user_id', '=', $user_id], + ['article_id', '=', $article_id], + ['status', '=', 1] + ])->get(['id', 'file_suffix', 'file_size', 'original_name', 'created_at'])->toArray(); + } } diff --git a/app/Service/UserService.php b/app/Service/UserService.php index 9535597..e4a1d3f 100644 --- a/app/Service/UserService.php +++ b/app/Service/UserService.php @@ -4,6 +4,7 @@ namespace App\Service; use App\Model\User; use App\Model\ArticleClass; +use Hyperf\DbConnection\Db; class UserService extends BaseService { @@ -37,8 +38,9 @@ class UserService extends BaseService public function register(array $data) { try { - $data['password'] = Hash::make($data['password']); + $data['password'] = create_password($data['password']); $data['created_at'] = date('Y-m-d H:i:s'); + $result = User::create($data); // 创建用户的默认笔记分类 @@ -49,9 +51,8 @@ class UserService extends BaseService 'sort' => 1, 'created_at' => time() ]); - } catch (Exception $e) { + } catch (\Exception $e) { $result = false; - DB::rollBack(); } return $result ? true : false; diff --git a/app/Traits/PagingTrait.php b/app/Traits/PagingTrait.php new file mode 100644 index 0000000..88094a7 --- /dev/null +++ b/app/Traits/PagingTrait.php @@ -0,0 +1,46 @@ + $rows, + 'page' => $page, + 'page_size' => $page_size, + 'page_total' => ($page_size == 0) ? 1 : $this->getPagingTotal($total, $page_size), + 'total' => $total, + ], $params); + } +} diff --git a/config/autoload/server.php b/config/autoload/server.php index 4127f5a..6c2aca3 100644 --- a/config/autoload/server.php +++ b/config/autoload/server.php @@ -40,7 +40,7 @@ return [ ], 'settings' => [ 'enable_coroutine' => true, - 'worker_num' => swoole_cpu_num(), + 'worker_num' => 1, 'pid_file' => BASE_PATH . '/runtime/hyperf.pid', 'open_tcp_nodelay' => true, 'max_coroutine' => 100000,