文章模块
发表文章
接口
@PreAuthorize("@permission.admin()")
@PostMapping
public ResponseResult postArticle(@RequestBody Article article) {
return articleService.postArticle(article);
}
此接口可以发表文章,也可以提交草稿内容
/**
* 后期可以去做一些定时发布的功能
* 如果是多人博客系统,得考虑审核的问题--->成功,通知,审核不通过,也可通知
* <p>
* 保存成草稿
* 1、用户手动提交:会发生页面跳转-->提交完即可
* 2、代码自动提交,每隔一段时间就会提交-->不会发生页面跳转-->多次提交-->如果没有唯一标识,会就重添加到数据库里
* <p>
* 不管是哪种草稿-->必须有标题
* <p>
* 方案一:每次用户发新文章之前-->先向后台请求一个唯一文章ID
* 如果是更新文件,则不需要请求这个唯一的ID
* <p>
* 方案二:可以直接提交,后台判断有没有ID,如果没有ID,就新创建,并且ID作为此次返回的结果
* 如果有ID,就修改已经存在的内容。
* <p>
* 推荐做法:
* 自动保存草稿,在前端本地完成,也就是保存在本地。
* 如果是用户手动提交的,就提交到后台
*
*
* <p>
* 防止重复提交(网络卡顿的时候,用户点了几次提交):
* 可以通过ID的方式
* 通过token_key的提交频率来计算,如果30秒之内有多次提交,只有最前的一次有效
* 其他的提交,直接return,提示用户不要太频繁操作.
* <p>
* 前端的处理:点击了提交以后,禁止按钮可以使用,等到有响应结果,再改变按钮的状态.
*
* @param article
* @return
*/
@Override
public ResponseResult postArticle(Article article) {
//检查用户,获取到用户对象
SobUser sobUser = userService.checkSobUser();
//未登录
if (sobUser == null) {
return ResponseResult.ACCOUNT_NOT_LOGIN();
}
//检查数据
//title、分类ID、内容、类型、摘要、标签
String title = article.getTitle();
if (TextUtils.isEmpty(title)) {
return ResponseResult.FAILED("标题不可以为空.");
}
//2种,草稿和发布
String state = article.getState();
if (!Constants.Article.STATE_PUBLISH.equals(state) &&
!Constants.Article.STATE_DRAFT.equals(state)) {
//不支持此操作
return ResponseResult.FAILED("不支持此操作");
}
String type = article.getType();
if (TextUtils.isEmpty(type)) {
return ResponseResult.FAILED("类型不可以为空.");
}
if (!"0".equals(type) && !"1".equals(type)) {
return ResponseResult.FAILED("类型格式不对.");
}
//以下检查是发布的检查,草稿不需要检查
if (Constants.Article.STATE_PUBLISH.equals(state)) {
if (title.length() > Constants.Article.TITLE_MAX_LENGTH) {
return ResponseResult.FAILED("文章标题不可以超过" + Constants.Article.TITLE_MAX_LENGTH + "个字符");
}
String content = article.getContent();
if (TextUtils.isEmpty(content)) {
return ResponseResult.FAILED("内容不可为空.");
}
String summary = article.getSummary();
if (TextUtils.isEmpty(summary)) {
return ResponseResult.FAILED("摘要不可以为空.");
}
if (summary.length() > Constants.Article.SUMMARY_MAX_LENGTH) {
return ResponseResult.FAILED("摘要不可以超出" + Constants.Article.SUMMARY_MAX_LENGTH + "个字符.");
}
String labels = article.getLabel();
//标签-标签1-标签2
if (TextUtils.isEmpty(labels)) {
return ResponseResult.FAILED("标签不可以为空.");
}
}
String articleId = article.getId();
if (TextUtils.isEmpty(articleId)) {
//新内容,数据里没有的
//补充数据:ID、创建时间、用户ID、更新时间
article.setId(idWorker.nextId() + "");
article.setCreateTime(new Date());
} else {
//更新内容,对状态进行处理,如果已经是发布的,则不能再保存为草稿
Article articleFromDb = articleDao.findOneById(articleId);
if (Constants.Article.STATE_PUBLISH.equals(articleFromDb.getState()) &&
Constants.Article.STATE_DRAFT.equals(state)) {
//已经发布了,只能更新,不能保存草稿
return ResponseResult.FAILED("已发布文章不支持成为草稿.");
}
}
article.setUserId(sobUser.getId());
article.setUpdateTime(new Date());
//保存到数据库里
articleDao.save(article);
//TODO:保存到搜索的数据库里
//打散标签,入库,统计
this.setupLabels(article.getLabel());
//返回结果,只有一种case使用到这个ID
//如果要做程序自动保存成草稿(比如说每30秒保存一次,就需要加上这个ID了,否则会创建多个Item)
return ResponseResult.SUCCESS(Constants.Article.STATE_DRAFT.equals(state) ? "草稿保存成功" :
"文章发表成功.").setData(article.getId());
}
另外还有标签统计,至于搜索的,我们在后面再补充吧
private void setupLabels(String labels) {
List<String> labelList = new ArrayList<>();
if (labels.contains("-")) {
labelList.addAll(Arrays.asList(labels.split("-")));
} else {
labelList.add(labels);
}
//入库,统计
for (String label : labelList) {
//找出来
//Label targetLabel = labelDao.findOneByName(label);
//if (targetLabel == null) {
// targetLabel = new Label();
// targetLabel.setId(idWorker.nextId() + "");
// targetLabel.setCount(0);
// targetLabel.setName(label);
// targetLabel.setCreateTime(new Date());
//}
//long count = targetLabel.getCount();
//targetLabel.setCount(++count);
//targetLabel.setUpdateTime(new Date());
int result = labelDao.updateCountByName(label);
if (result == 0) {
Label targetLabel = new Label();
targetLabel.setId(idWorker.nextId() + "");
targetLabel.setCount(1);
targetLabel.setName(label);
targetLabel.setCreateTime(new Date());
targetLabel.setUpdateTime(new Date());
labelDao.save(targetLabel);
}
}
}
更新文章
调用场景:文章已经发表了,我们需要更新内容,则会调用此接口进行更新。
@PreAuthorize("@permission.admin()")
@PutMapping("/{articleId}")
public ResponseResult updateArticle(@PathVariable("articleId") String articleId, @RequestBody Article article) {
return articleService.updateArticle(articleId, article);
}
接口实现
/**
* 更新文章内容
* <p>
* 该接口只支持修改内容:标题、内容、标签、分类,摘要
*
* @param articleId 文章ID
* @param article 文章
* @return
*/
@Override
public ResponseResult updateArticle(String articleId, Article article) {
//先找出来
Article articleFromDb = articleDao.findOneById(articleId);
if (articleFromDb == null) {
return ResponseResult.FAILED("文章不存在.");
}
//内容修改
String title = article.getTitle();
if (!TextUtils.isEmpty(title)) {
articleFromDb.setTitle(title);
}
String summary = article.getSummary();
if (!TextUtils.isEmpty(summary)) {
articleFromDb.setSummary(summary);
}
String content = article.getContent();
if (!TextUtils.isEmpty(content)) {
articleFromDb.setContent(content);
}
String label = article.getLabel();
if (!TextUtils.isEmpty(label)) {
articleFromDb.setLabel(label);
}
String categoryId = article.getCategoryId();
if (!TextUtils.isEmpty(categoryId)) {
articleFromDb.setCategoryId(categoryId);
}
articleFromDb.setCover(article.getCover());
articleFromDb.setUpdateTime(new Date());
articleDao.save(articleFromDb);
//返回结果
return ResponseResult.SUCCESS("文章更新成功.");
}
删除文章
文章的删除,我们不会真的删除文章,而是修改它的状态。因为还有评论之类的跟文章相关。
以前的做法则是删除了文章,同是会删除对应的评论,对应的图片之类的...
物理删除:
/**
* 如果是多用户,用户不可以删除,删除只是修改状态
* 管理可以删除
* <p>
* 做成真的删除
*
* @param articleId
* @return
*/
@PreAuthorize("@permission.admin()")
@DeleteMapping("/{articleId}")
public ResponseResult deleteArticle(@PathVariable("articleId") String articleId) {
return articleService.deleteArticleById(articleId);
}
这个是真的删除
/**
* 删除文章,物理删除
*
* @param articleId
* @return
*/
@Override
public ResponseResult deleteArticleById(String articleId) {
commentDao.deleteAllByArticleId(articleId);
int result = articleDao.deleteAllById(articleId);
if (result > 0) {
return ResponseResult.SUCCESS("文章删除成功.");
}
return ResponseResult.FAILED("文章不存在.");
}
现在呢,我们只改它的状态。
@PreAuthorize("@permission.admin()")
@DeleteMapping("/sate/{articleId}")
public ResponseResult deleteArticleByUpdateState(@PathVariable("articleId") String articleId) {
return articleService.deleteArticleByState(articleId);
}
接口实现
/**
* 通过修改状态删除文章,标记删除
*
* @param articleId
* @return
*/
@Override
public ResponseResult deleteArticleByState(String articleId) {
int result = articleDao.deleteArticleByState(articleId);
if (result > 0) {
return ResponseResult.SUCCESS("文章删除成功.");
}
return ResponseResult.FAILED("文章不存在.");
}
DAO语句:
@Modifying
@Query(nativeQuery = true, value = "update `tb_article` set `state` = '0' where `id` = ? ")
int deleteArticleByState(String articleId);
获取文章详情
获取文章详情,我们要设置一下viewCount,另外则是做缓存处理。
缓存我们一般会统一处理。因为要考虑添加缓存和删除缓存
比如说,我们在访问文章的时候,添加缓存,先从缓存中获取,如果没有再去数据库中获取,获取到了再添加到缓存里。
如果我们更新文章、置顶文章、删除文章、就需要去清除缓存。等待下次获取文章详情的时候,重新加入缓存里。
@PreAuthorize("@permission.admin()")
@GetMapping("/{articleId}")
public ResponseResult getArticle(@PathVariable("articleId") String articleId) {
return articleService.getArticleById(articleId);
}
/**
* 如果有审核机制:审核中的文章-->只有管理员和作者自己可以获取
* 有草稿、删除、置顶的、已经发布的
* 删除的不能获取、其他都可以获取
*
* @param articleId
* @return
*/
@Override
public ResponseResult getArticleById(String articleId) {
//查询出文章
Article article = articleDao.findOneById(articleId);
if (article == null) {
return ResponseResult.FAILED("文章不存在.");
}
//判断文章状态
String state = article.getState();
if (Constants.Article.STATE_PUBLISH.equals(state) ||
Constants.Article.STATE_TOP.equals(state)) {
//可以返回
return ResponseResult.SUCCESS("获取文章成功.").setData(article);
}
//如果是删除/草稿,需要管理角色
SobUser sobUser = userService.checkSobUser();
if (sobUser == null || !Constants.User.ROLE_ADMIN.equals(sobUser.getRoles())) {
return ResponseResult.PERMISSION_DENIED();
}
//返回结果
return ResponseResult.SUCCESS("获取文章成功.").setData(article);
}
还要注意的是权限,管理员可以拿到任何状态的文章,作者可以拿到除了删除状态的文章,其他人只能拿到发布,或者置顶的文章。
获取文章列表(条件查询,不包含内容)
条件获取文章列表
@PreAuthorize("@permission.admin()")
@GetMapping("/list/{page}/{size}")
public ResponseResult listArticles(@PathVariable("page") int page,
@PathVariable("size") int size,
@RequestParam(value = "state", required = false) String state,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "categoryId", required = false) String categoryId) {
return articleService.listArticles(page, size, keyword, categoryId, state);
}
这里的条件包括状态呀,标题搜索,分类这些。
后面我们还有一个label的条件,独立出来了。
/**
* 这里管理中,获取文章列表
*
* @param page 页码
* @param size 每一页数量
* @param keyword 标题关键字(搜索关键字)
* @param categoryId 分类ID
* @param state 状态:已经删除、草稿、已经发布的、置顶的
* @return
*/
@Override
public ResponseResult listArticles(int page, int size, String keyword,
String categoryId, String state) {
//处理一下size 和page
page = checkPage(page);
size = checkSize(size);
//创建分页和排序条件
Sort sort = new Sort(Sort.Direction.DESC, "createTime");
Pageable pageable = PageRequest.of(page - 1, size, sort);
//开始查询
Page<ArticleNoContent> all = articleNoContentDao.findAll(new Specification<ArticleNoContent>() {
@Override
public Predicate toPredicate(Root<ArticleNoContent> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
//判断是否有传参数
if (!TextUtils.isEmpty(state)) {
Predicate statePre = cb.equal(root.get("state").as(String.class), state);
predicates.add(statePre);
}
if (!TextUtils.isEmpty(categoryId)) {
Predicate categoryIdPre = cb.equal(root.get("categoryId").as(String.class), categoryId);
predicates.add(categoryIdPre);
}
if (!TextUtils.isEmpty(keyword)) {
Predicate titlePre = cb.like(root.get("title").as(String.class), "%" + keyword + "%");
predicates.add(titlePre);
}
Predicate[] preArray = new Predicate[predicates.size()];
predicates.toArray(preArray);
return cb.and(preArray);
}
}, pageable);
//处理查询条件
//返回结果
return ResponseResult.SUCCESS("获取文章列表成功.").setData(all);
}
文章置顶
接口
@PreAuthorize("@permission.admin()")
@PutMapping("/top/{articleId}")
public ResponseResult topArticle(@PathVariable("articleId") String articleId) {
return articleService.topArticle(articleId);
}
文章置顶是独立开来的,因为我们希望不管用户翻到哪一页内容,我们都把这些文章置顶。
多次调用:文章置顶会取消,取消了的则会置顶
实现接口:
@Override
public ResponseResult topArticle(String articleId) {
//必须已经发布的,才可以置顶
Article article = articleDao.findOneById(articleId);
if (article == null) {
return ResponseResult.FAILED("文章不存在.");
}
String state = article.getState();
if (Constants.Article.STATE_PUBLISH.equals(state)) {
article.setState(Constants.Article.STATE_TOP);
articleDao.save(article);
return ResponseResult.SUCCESS("文章置顶成功.");
}
if (Constants.Article.STATE_TOP.equals(state)) {
article.setState(Constants.Article.STATE_PUBLISH);
articleDao.save(article);
return ResponseResult.SUCCESS("已取消置顶.");
}
return ResponseResult.FAILED("不支持该操作.");
}