• 72:第六章:开发文章服务:5:开发【发表文章,接口】;


    说明:

    (1)本篇博客内容,开发【发表文章,接口】;

    目录

    一:本篇博客内容,开发【发表文章,接口】;

    二:开发【发表文章,接口】:Controller部分;

    1.在【api】接口工程中,创建ArticleControllerApi接口,定义【发表文章,接口】;

    2.在【article】文章服务中,创建ArticleController类,去实现【发表文章,接口】;

    三:开发【发表文章,接口】:Service部分:去创建一个发表文章的方法;

    1.创建ArticleService接口,定义一个发表文章的方法;

    2.创建ArticleServiceImpl实现类,去实现发表文章的方法; 

    附加一:利用【mybatis-generator-database】工程,根据article表,生成【实体类、mapper接口、mapper.xml】;

    四:效果;


    一:本篇博客内容,开发【发表文章,接口】;


    二:开发【发表文章,接口】:Controller部分;

    1.在【api】接口工程中,创建ArticleControllerApi接口,定义【发表文章,接口】;

    1. package com.imooc.api.controller.article;
    2. import com.imooc.bo.NewArticleBO;
    3. import com.imooc.grace.result.GraceJSONResult;
    4. import io.swagger.annotations.Api;
    5. import io.swagger.annotations.ApiOperation;
    6. import org.springframework.validation.BindingResult;
    7. import org.springframework.web.bind.annotation.PostMapping;
    8. import org.springframework.web.bind.annotation.RequestBody;
    9. import org.springframework.web.bind.annotation.RequestMapping;
    10. import javax.validation.Valid;
    11. @Api(value = "article文章相关Controller",tags = {"article文章相关Controller"})
    12. @RequestMapping("article") //设置路由,这个是需要前后端约定好的;
    13. public interface ArticleControllerApi {
    14. /**
    15. * 【发表文章,接口】
    16. * @param newArticleBO:使用NewArticleBO来承接文章数据;
    17. * @param result
    18. * @return
    19. */
    20. @ApiOperation(value = "用户发表文章", notes = "用户发表文章", httpMethod = "POST")
    21. @PostMapping("/createArticle") //设置路由,这个是需要前后端约定好的;
    22. public GraceJSONResult adminLogin(@RequestBody @Valid NewArticleBO newArticleBO,
    23. BindingResult result);
    24. }

    说明:

    (1)该接口的url、请求方式、参数不是瞎写的,需要前后端保持一致;

    (2)我们在【model】模型工程中,创建NewArticleBO类来承接参数;

    1. package com.imooc.bo;
    2. import com.fasterxml.jackson.annotation.JsonFormat;
    3. import org.hibernate.validator.constraints.Length;
    4. import javax.validation.constraints.Max;
    5. import javax.validation.constraints.Min;
    6. import javax.validation.constraints.NotBlank;
    7. import javax.validation.constraints.NotNull;
    8. import java.util.Date;
    9. /**
    10. * 用户发文的BO
    11. */
    12. public class NewArticleBO {
    13. @NotBlank(message = "文章标题不能为空")
    14. @Length(max = 30, message = "文章标题长度不能超过30")
    15. private String title;
    16. @NotBlank(message = "文章内容不能为空")
    17. @Length(max = 9999, message = "文章内容长度不能超过10000")
    18. private String content;
    19. /**
    20. * 即,文章分类是必须要写的;
    21. * 而"前端传过来的文章分类"不在"数据库category表"中的判断逻辑,会在后端去做;
    22. */
    23. @NotNull(message = "请选择文章领域")
    24. private Integer categoryId;
    25. @NotNull(message = "请选择正确的文章封面类型")
    26. @Min(value = 1, message = "请选择正确的文章封面类型")
    27. @Max(value = 2, message = "请选择正确的文章封面类型")
    28. private Integer articleType;
    29. private String articleCover;
    30. @NotNull(message = "文章发布类型不正确")
    31. @Min(value = 0, message = "文章发布类型不正确")
    32. @Max(value = 1, message = "文章发布类型不正确")
    33. private Integer isAppoint;
    34. @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") // 前端日期字符串传到后端后,转换为Date类型
    35. private Date publishTime;
    36. @NotBlank(message = "用户未登录")
    37. private String publishUserId;
    38. public String getTitle() {
    39. return title;
    40. }
    41. public void setTitle(String title) {
    42. this.title = title;
    43. }
    44. public Integer getCategoryId() {
    45. return categoryId;
    46. }
    47. public void setCategoryId(Integer categoryId) {
    48. this.categoryId = categoryId;
    49. }
    50. public Integer getArticleType() {
    51. return articleType;
    52. }
    53. public void setArticleType(Integer articleType) {
    54. this.articleType = articleType;
    55. }
    56. public String getArticleCover() {
    57. return articleCover;
    58. }
    59. public void setArticleCover(String articleCover) {
    60. this.articleCover = articleCover;
    61. }
    62. public Integer getIsAppoint() {
    63. return isAppoint;
    64. }
    65. public void setIsAppoint(Integer isAppoint) {
    66. this.isAppoint = isAppoint;
    67. }
    68. public String getPublishUserId() {
    69. return publishUserId;
    70. }
    71. public void setPublishUserId(String publishUserId) {
    72. this.publishUserId = publishUserId;
    73. }
    74. public String getContent() {
    75. return content;
    76. }
    77. public void setContent(String content) {
    78. this.content = content;
    79. }
    80. public Date getPublishTime() {
    81. return publishTime;
    82. }
    83. public void setPublishTime(Date publishTime) {
    84. this.publishTime = publishTime;
    85. }
    86. @Override
    87. public String toString() {
    88. return "NewArticleBO{" +
    89. "title='" + title + '\'' +
    90. ", content='" + content + '\'' +
    91. ", categoryId=" + categoryId +
    92. ", articleType=" + articleType +
    93. ", articleCover='" + articleCover + '\'' +
    94. ", isAppoint=" + isAppoint +
    95. ", publishTime=" + publishTime +
    96. ", publishUserId='" + publishUserId + '\'' +
    97. '}';
    98. }
    99. }

              ● 这BO类,使用了大量的validation参数校验的内容; 

              ● 其中的"@JsonFormat"主要作用是处理前后端传递日期格式数据时的格式问题;具体可以参考【RESTful开发风格6:RESTful基本使用四:JSON序列化;】等文章;

              ● 用户在发表文章时,当向文章中贴图片、设置文章封面的时候;前端会自己去调用【多文件上传,接口】把图片文件上传到阿里OSS上,然后阿里OSS会返回图片的地址url;然后,前端在调用本篇博客的【发表文章,接口】时,这些图片的地址会传送到后端;

    2.在【article】文章服务中,创建ArticleController类,去实现【发表文章,接口】;

    1. package com.imooc.article.controller;
    2. import com.imooc.api.BaseController;
    3. import com.imooc.api.controller.article.ArticleControllerApi;
    4. import com.imooc.api.controller.user.HelloControllerApi;
    5. import com.imooc.article.service.ArticleService;
    6. import com.imooc.bo.NewArticleBO;
    7. import com.imooc.enums.ArticleCoverType;
    8. import com.imooc.grace.result.GraceJSONResult;
    9. import com.imooc.grace.result.ResponseStatusEnum;
    10. import com.imooc.pojo.Category;
    11. import com.imooc.utils.JsonUtils;
    12. import org.apache.commons.lang3.StringUtils;
    13. import org.slf4j.Logger;
    14. import org.slf4j.LoggerFactory;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.validation.BindingResult;
    17. import org.springframework.web.bind.annotation.RestController;
    18. import javax.validation.Valid;
    19. import java.util.List;
    20. import java.util.Map;
    21. @RestController
    22. public class ArticleController extends BaseController implements ArticleControllerApi {
    23. final static Logger logger = LoggerFactory.getLogger(ArticleController.class);
    24. @Autowired
    25. private ArticleService articleService;
    26. /**
    27. * 【发表文章,接口】
    28. * @param newArticleBO:使用NewArticleBO来承接文章数据;
    29. * @param result
    30. * @return
    31. */
    32. @Override
    33. public GraceJSONResult createArticle(@Valid NewArticleBO newArticleBO,
    34. BindingResult result) {
    35. //0.判断BindingResult中是否保存了验证失败的错误信息,如果有,说明前端的输入是有问题的(用户名或者密码,至少有一个没输入);
    36. // 那么,我们就获取这个错误信息,并构建一个GraceJSONResult统一返回对象,返回;
    37. if (result.hasErrors()) {
    38. Map map = getErrorsFromBindingResult(result);
    39. return GraceJSONResult.errorMap(map);
    40. }
    41. /**
    42. * 1.判断"文章封面类型"(即NewArticleBO类的articleType字段):
    43. * 如果文章是有封面的,那么NewArticleBO类的articleCover属性不能为空;
    44. * 如果文章是没有封面的,那么NewArticleBO类的articleCover属性设置为空;
    45. */
    46. //如果文章类型是图文的(即有一张封面),那么"封面图片的地址,是一定要有的",即articleCover属性不能为空
    47. if (newArticleBO.getArticleType() == ArticleCoverType.ONE_IMAGE.type) {
    48. //此时,如果articleCover属性为空了,直接返回一个信息是"文章封面不存在,请选择一个!"的GraceJSONResult;
    49. if (StringUtils.isBlank(newArticleBO.getArticleCover())) {
    50. return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_COVER_NOT_EXIST_ERROR);
    51. }
    52. }
    53. //如果文章类型是纯文字的,那么前端articleCover属性是不是为空,无所谓;我们直接把其设置为空即可;
    54. else if (newArticleBO.getArticleType() == ArticleCoverType.WORDS.type) {
    55. newArticleBO.setArticleCover("");
    56. }
    57. /**
    58. * 2.判断"前端传过来的文章分类"需要在"数据库category表"中;
    59. * 做法1:根据前端传过来categoryId,直接去数据库的category表中查,看有没有;(但是,
    60. * 由于【发表文章,接口】是提供给用户的,自然其并发量会是很大的;所以,在这个接口中,
    61. * 每次都去查询数据库,其实是不太好的;)
    62. * 做法2:根据前端传过来categoryId,redis中查;(我们在开发【查看文章领域,接口】的时
    63. * 候,把文章分类数据存到了缓存中;)这儿,我们采用做法2;
    64. */
    65. //从redis中,获取所有文章分类的JSON字符串
    66. String allCategoryJSON= redisOperator.get(REDIS_ALL_CATEGORY);
    67. //
    68. /**
    69. * 如果redis中没有文章分类数据,那么说明系统出了一些相应的问题;我们这儿就可以返回一些提示信息;比如这儿
    70. * 我们可以返回一个信息是"操作失败,请重试或联系管理员"的GraceJSONResult;(PS:这种情况出现的概率,极低)
    71. */
    72. Category temp = null;
    73. if (StringUtils.isBlank(allCategoryJSON)) {
    74. return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_OPERATION_ERROR);
    75. } else {
    76. //调用JsonUtils工具类,把JSON转成List;
    77. List list = JsonUtils.jsonToList(allCategoryJSON, Category.class);
    78. for (Category category : list) {
    79. if (category.getId() == newArticleBO.getCategoryId()) {
    80. temp = category;
    81. break;//如果找到了,就把找到的赋值给temp,并直接终止循环
    82. }
    83. }
    84. //如果,至此temp还是为空的话,就说明"前端传过来的文章分类"是不合法的;
    85. if (temp == null) {
    86. //返回一个信息是"请选择正确的文章领域!"的GraceJSONResult;
    87. return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_CATEGORY_NOT_EXIST_ERROR);
    88. }
    89. }
    90. // 3. 调用Service层的方法,去新增文章;
    91. articleService.createArticle(newArticleBO, temp);
    92. return GraceJSONResult.ok();
    93. }
    94. }

    说明:

    (1)基本逻辑是: 【先判断前端传过来的参数是否OK】→【然后,判断文章类型(是否有封面)和封面图片地址,是否OK】→【然后,判断"前端传过来的文章分类"是否合法】→【然后,调用service层的方法,去发表文章】

    (2)在【common】通用工程中,定义了一个枚举类ArticleCoverType,来表征article_type即文章类型,也就是说文章是否有封面;

    (3)Service层的内容,在三部分中,有介绍;


    三:开发【发表文章,接口】:Service部分:去创建一个发表文章的方法;

    在【article】文章服务中,创建service包,impl包,ArticleService接口,ArticleServiceImpl实现类; 

    1.创建ArticleService接口,定义一个发表文章的方法;

    1. package com.imooc.article.service;
    2. import com.imooc.bo.NewArticleBO;
    3. import com.imooc.pojo.Category;
    4. import java.io.IOException;
    5. /**
    6. * 定义与【article文章】相关的方法;
    7. */
    8. public interface ArticleService {
    9. /**
    10. * 发表文章的方法;
    11. * @param newArticleBO
    12. * @param category
    13. * @return
    14. * @throws IOException
    15. */
    16. public void createArticle(NewArticleBO newArticleBO,
    17. Category category);
    18. }

    2.创建ArticleServiceImpl实现类,去实现发表文章的方法; 

    1. package com.imooc.article.service.impl;
    2. import com.imooc.api.service.BaseService;
    3. import com.imooc.article.mapper.ArticleMapper;
    4. import com.imooc.article.service.ArticleService;
    5. import com.imooc.bo.NewArticleBO;
    6. import com.imooc.enums.ArticleAppointType;
    7. import com.imooc.enums.ArticleReviewStatus;
    8. import com.imooc.enums.YesOrNo;
    9. import com.imooc.exception.GraceException;
    10. import com.imooc.grace.result.ResponseStatusEnum;
    11. import com.imooc.pojo.Article;
    12. import com.imooc.pojo.Category;
    13. import org.n3r.idworker.Sid;
    14. import org.springframework.beans.BeanUtils;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.stereotype.Service;
    17. import java.io.IOException;
    18. import java.util.Date;
    19. @Service
    20. public class ArticleServiceImpl extends BaseService implements ArticleService {
    21. @Autowired
    22. private ArticleMapper articleMapper;
    23. @Autowired
    24. private Sid sid;//新增文章时,article表的主键,利用Sid生成;
    25. /**
    26. * 发表文章的方法;
    27. * @param newArticleBO
    28. * @param category
    29. * @return
    30. * @throws IOException
    31. */
    32. @Transactional
    33. @Override
    34. public void createArticle(NewArticleBO newArticleBO, Category category){
    35. String articleId = sid.nextShort();//利用sid生成一个随机字符串,作为article表的id;
    36. Article article = new Article();
    37. BeanUtils.copyProperties(newArticleBO, article);//属性copy,把newArticleBO中的属性,copy到article对象上去;
    38. article.setId(articleId);//设置主键
    39. article.setCategoryId(category.getId());//设置categoryId
    40. article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);//刚提交的文章,其文章状态设为"审核中";
    41. article.setCommentCounts(0);//文章初始评论数量设为0
    42. article.setReadCounts(0);//文章初始阅读数量设为0
    43. article.setIsDelete(YesOrNo.NO.type);//文章的"逻辑删除状态",设为0(即在article表中,代表未删除)
    44. article.setCreateTime(new Date());//设置创建时间
    45. article.setUpdateTime(new Date());//设置更新时间
    46. /**
    47. * 如果文章是定时发布的,就设置publish_time为预约发布时间;
    48. * 如果文章不是定时发布,而是立即发布,就设置publish_time为当前发布时间;
    49. */
    50. if (article.getIsAppoint() == ArticleAppointType.TIMING.type) {
    51. article.setPublishTime(newArticleBO.getPublishTime());
    52. } else {
    53. article.setPublishTime(new Date());
    54. }
    55. //调用Dao层的方法,去插入
    56. int res = articleMapper.insert(article);
    57. if (res != 1) {//如果插入失败,就抛出一个信息是"创建文章失败,请重试或联系管理员!"的自定义异常;
    58. GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR);
    59. }
    60. }
    61. }

    说明:

    (1) 看注释;

    (2)在【common】通用工程中,创建了ArticleReviewStatus枚举类;来定义文章的审核状态;

    (3)在【common】通用工程中,创建了YesOrNo枚举类;

    (4)在【common】通用工程中,创建了ArticleAppointType枚举类;来表征文章是否是定时发布;

    (5)如果设置属性方面,有不清楚的,看下article表,NewArticleBO类,Article类,就明白了;


    附加一:利用【mybatis-generator-database】工程,根据article表,生成【实体类、mapper接口、mapper.xml】;

    利用【15:第二章:架构后端项目:11:使用【mybatis-generator-core】依赖生成实体类、mapper接口、mapper.xml等逆向文件;】中,创建的【mybatis-generator-database】工程;根据article表,生成【实体类、mapper接口、mapper.xml】;

              ● 把pojo实体类,放到【model】中;

              ● 把mapper接口、mapper.xml,放到【article】中;


    四:效果;

    (1)先install一下整个项目;(2)记得使用SwitchHost开启虚拟域名映射;(3)使用Tomcat启动前端项目;(4)然后,启动后端项目;

    ……………………………………………………

    可以看到,"文章中的图片"和"文章封面图",在数据库表中存放的都是"图片上传到阿里OSS中的url地址";

    ……………………………………………………

    ……………………………………………………

    可以看到,我们打印了前端传给后端的NewArticleBO内容:

    NewArticleBO{title='测试文章11', content='<p>发生的附带上p><p><img src="https://imooc-news-dev-wgy.oss-cn-shanghai.aliyuncs.com/image/face/220714DCNP2XK9GC/220812C06BYTMMRP.png"><br>p><p>第三方附带上p>', categoryId=6, articleType=1, articleCover='https://imooc-news-dev-wgy.oss-cn-shanghai.aliyuncs.com/image/face/220714DCNP2XK9GC/220812C07A6P5M14.png', isAppoint=0, publishTime=null, publishUserId='220714DCNP2XK9GC'}
    

    可以看到,我们没有选择定时发布的话,前端传过来的publisTime是null;

  • 相关阅读:
    【深度学习 Pytorch笔记 B站刘二大人 线性模型 Linear-Model 数学原理分析以及源码实现(1/10)】
    WebSocket、服务器推送技术
    使用nginx发布tomcat站点
    SpringBoot + Docker 实现一次构建到处运行
    【卷王秘籍】学了三遍操作系统后,榨干知识点,让面试官自闭!
    [MAUI]深入了解.NET MAUI Blazor与Vue的混合开发
    时序预测 | MATLAB实现贝叶斯优化CNN-BiLSTM时间序列预测(股票价格预测)
    Dapper数据库字段(列)与实体属性名不一致,通过Column特性自动注入映射
    关于Docker compose值IP与域名的映射 之 extra_host
    PhiData 一款开发AI搜索、agents智能体和工作流应用的AI框架
  • 原文地址:https://blog.csdn.net/csucsgoat/article/details/126300316