说明:
(1)本篇博客内容,开发【发表文章,接口】;
目录
1.在【api】接口工程中,创建ArticleControllerApi接口,定义【发表文章,接口】;
2.在【article】文章服务中,创建ArticleController类,去实现【发表文章,接口】;
三:开发【发表文章,接口】:Service部分:去创建一个发表文章的方法;
1.创建ArticleService接口,定义一个发表文章的方法;
2.创建ArticleServiceImpl实现类,去实现发表文章的方法;
附加一:利用【mybatis-generator-database】工程,根据article表,生成【实体类、mapper接口、mapper.xml】;
package com.imooc.api.controller.article; import com.imooc.bo.NewArticleBO; import com.imooc.grace.result.GraceJSONResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import javax.validation.Valid; @Api(value = "article文章相关Controller",tags = {"article文章相关Controller"}) @RequestMapping("article") //设置路由,这个是需要前后端约定好的; public interface ArticleControllerApi { /** * 【发表文章,接口】 * @param newArticleBO:使用NewArticleBO来承接文章数据; * @param result * @return */ @ApiOperation(value = "用户发表文章", notes = "用户发表文章", httpMethod = "POST") @PostMapping("/createArticle") //设置路由,这个是需要前后端约定好的; public GraceJSONResult adminLogin(@RequestBody @Valid NewArticleBO newArticleBO, BindingResult result); }说明:
(1)该接口的url、请求方式、参数不是瞎写的,需要前后端保持一致;
(2)我们在【model】模型工程中,创建NewArticleBO类来承接参数;
package com.imooc.bo; import com.fasterxml.jackson.annotation.JsonFormat; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.Date; /** * 用户发文的BO */ public class NewArticleBO { @NotBlank(message = "文章标题不能为空") @Length(max = 30, message = "文章标题长度不能超过30") private String title; @NotBlank(message = "文章内容不能为空") @Length(max = 9999, message = "文章内容长度不能超过10000") private String content; /** * 即,文章分类是必须要写的; * 而"前端传过来的文章分类"不在"数据库category表"中的判断逻辑,会在后端去做; */ @NotNull(message = "请选择文章领域") private Integer categoryId; @NotNull(message = "请选择正确的文章封面类型") @Min(value = 1, message = "请选择正确的文章封面类型") @Max(value = 2, message = "请选择正确的文章封面类型") private Integer articleType; private String articleCover; @NotNull(message = "文章发布类型不正确") @Min(value = 0, message = "文章发布类型不正确") @Max(value = 1, message = "文章发布类型不正确") private Integer isAppoint; @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") // 前端日期字符串传到后端后,转换为Date类型 private Date publishTime; @NotBlank(message = "用户未登录") private String publishUserId; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Integer getCategoryId() { return categoryId; } public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; } public Integer getArticleType() { return articleType; } public void setArticleType(Integer articleType) { this.articleType = articleType; } public String getArticleCover() { return articleCover; } public void setArticleCover(String articleCover) { this.articleCover = articleCover; } public Integer getIsAppoint() { return isAppoint; } public void setIsAppoint(Integer isAppoint) { this.isAppoint = isAppoint; } public String getPublishUserId() { return publishUserId; } public void setPublishUserId(String publishUserId) { this.publishUserId = publishUserId; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getPublishTime() { return publishTime; } public void setPublishTime(Date publishTime) { this.publishTime = publishTime; } @Override public String toString() { return "NewArticleBO{" + "title='" + title + '\'' + ", content='" + content + '\'' + ", categoryId=" + categoryId + ", articleType=" + articleType + ", articleCover='" + articleCover + '\'' + ", isAppoint=" + isAppoint + ", publishTime=" + publishTime + ", publishUserId='" + publishUserId + '\'' + '}'; } }● 这BO类,使用了大量的validation参数校验的内容;
● 其中的"@JsonFormat"主要作用是处理前后端传递日期格式数据时的格式问题;具体可以参考【RESTful开发风格6:RESTful基本使用四:JSON序列化;】等文章;
● 用户在发表文章时,当向文章中贴图片、设置文章封面的时候;前端会自己去调用【多文件上传,接口】把图片文件上传到阿里OSS上,然后阿里OSS会返回图片的地址url;然后,前端在调用本篇博客的【发表文章,接口】时,这些图片的地址会传送到后端;
package com.imooc.article.controller; import com.imooc.api.BaseController; import com.imooc.api.controller.article.ArticleControllerApi; import com.imooc.api.controller.user.HelloControllerApi; import com.imooc.article.service.ArticleService; import com.imooc.bo.NewArticleBO; import com.imooc.enums.ArticleCoverType; import com.imooc.grace.result.GraceJSONResult; import com.imooc.grace.result.ResponseStatusEnum; import com.imooc.pojo.Category; import com.imooc.utils.JsonUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import java.util.List; import java.util.Map; @RestController public class ArticleController extends BaseController implements ArticleControllerApi { final static Logger logger = LoggerFactory.getLogger(ArticleController.class); @Autowired private ArticleService articleService; /** * 【发表文章,接口】 * @param newArticleBO:使用NewArticleBO来承接文章数据; * @param result * @return */ @Override public GraceJSONResult createArticle(@Valid NewArticleBO newArticleBO, BindingResult result) { //0.判断BindingResult中是否保存了验证失败的错误信息,如果有,说明前端的输入是有问题的(用户名或者密码,至少有一个没输入); // 那么,我们就获取这个错误信息,并构建一个GraceJSONResult统一返回对象,返回; if (result.hasErrors()) { Mapmap = getErrorsFromBindingResult(result); return GraceJSONResult.errorMap(map); } /** * 1.判断"文章封面类型"(即NewArticleBO类的articleType字段): * 如果文章是有封面的,那么NewArticleBO类的articleCover属性不能为空; * 如果文章是没有封面的,那么NewArticleBO类的articleCover属性设置为空; */ //如果文章类型是图文的(即有一张封面),那么"封面图片的地址,是一定要有的",即articleCover属性不能为空 if (newArticleBO.getArticleType() == ArticleCoverType.ONE_IMAGE.type) { //此时,如果articleCover属性为空了,直接返回一个信息是"文章封面不存在,请选择一个!"的GraceJSONResult; if (StringUtils.isBlank(newArticleBO.getArticleCover())) { return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_COVER_NOT_EXIST_ERROR); } } //如果文章类型是纯文字的,那么前端articleCover属性是不是为空,无所谓;我们直接把其设置为空即可; else if (newArticleBO.getArticleType() == ArticleCoverType.WORDS.type) { newArticleBO.setArticleCover(""); } /** * 2.判断"前端传过来的文章分类"需要在"数据库category表"中; * 做法1:根据前端传过来categoryId,直接去数据库的category表中查,看有没有;(但是, * 由于【发表文章,接口】是提供给用户的,自然其并发量会是很大的;所以,在这个接口中, * 每次都去查询数据库,其实是不太好的;) * 做法2:根据前端传过来categoryId,redis中查;(我们在开发【查看文章领域,接口】的时 * 候,把文章分类数据存到了缓存中;)这儿,我们采用做法2; */ //从redis中,获取所有文章分类的JSON字符串 String allCategoryJSON= redisOperator.get(REDIS_ALL_CATEGORY); // /** * 如果redis中没有文章分类数据,那么说明系统出了一些相应的问题;我们这儿就可以返回一些提示信息;比如这儿 * 我们可以返回一个信息是"操作失败,请重试或联系管理员"的GraceJSONResult;(PS:这种情况出现的概率,极低) */ Category temp = null; if (StringUtils.isBlank(allCategoryJSON)) { return GraceJSONResult.errorCustom(ResponseStatusEnum.SYSTEM_OPERATION_ERROR); } else { //调用JsonUtils工具类,把JSON转成List; Listlist = JsonUtils.jsonToList(allCategoryJSON, Category.class); for (Category category : list) { if (category.getId() == newArticleBO.getCategoryId()) { temp = category; break;//如果找到了,就把找到的赋值给temp,并直接终止循环 } } //如果,至此temp还是为空的话,就说明"前端传过来的文章分类"是不合法的; if (temp == null) { //返回一个信息是"请选择正确的文章领域!"的GraceJSONResult; return GraceJSONResult.errorCustom(ResponseStatusEnum.ARTICLE_CATEGORY_NOT_EXIST_ERROR); } } // 3. 调用Service层的方法,去新增文章; articleService.createArticle(newArticleBO, temp); return GraceJSONResult.ok(); } }说明:
(1)基本逻辑是: 【先判断前端传过来的参数是否OK】→【然后,判断文章类型(是否有封面)和封面图片地址,是否OK】→【然后,判断"前端传过来的文章分类"是否合法】→【然后,调用service层的方法,去发表文章】
(2)在【common】通用工程中,定义了一个枚举类ArticleCoverType,来表征article_type即文章类型,也就是说文章是否有封面;
(3)Service层的内容,在三部分中,有介绍;
在【article】文章服务中,创建service包,impl包,ArticleService接口,ArticleServiceImpl实现类;
- package com.imooc.article.service;
-
- import com.imooc.bo.NewArticleBO;
- import com.imooc.pojo.Category;
-
- import java.io.IOException;
-
- /**
- * 定义与【article文章】相关的方法;
- */
- public interface ArticleService {
-
- /**
- * 发表文章的方法;
- * @param newArticleBO
- * @param category
- * @return
- * @throws IOException
- */
- public void createArticle(NewArticleBO newArticleBO,
- Category category);
- }
package com.imooc.article.service.impl; import com.imooc.api.service.BaseService; import com.imooc.article.mapper.ArticleMapper; import com.imooc.article.service.ArticleService; import com.imooc.bo.NewArticleBO; import com.imooc.enums.ArticleAppointType; import com.imooc.enums.ArticleReviewStatus; import com.imooc.enums.YesOrNo; import com.imooc.exception.GraceException; import com.imooc.grace.result.ResponseStatusEnum; import com.imooc.pojo.Article; import com.imooc.pojo.Category; import org.n3r.idworker.Sid; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.Date; @Service public class ArticleServiceImpl extends BaseService implements ArticleService { @Autowired private ArticleMapper articleMapper; @Autowired private Sid sid;//新增文章时,article表的主键,利用Sid生成; /** * 发表文章的方法; * @param newArticleBO * @param category * @return * @throws IOException */ @Transactional @Override public void createArticle(NewArticleBO newArticleBO, Category category){ String articleId = sid.nextShort();//利用sid生成一个随机字符串,作为article表的id; Article article = new Article(); BeanUtils.copyProperties(newArticleBO, article);//属性copy,把newArticleBO中的属性,copy到article对象上去; article.setId(articleId);//设置主键 article.setCategoryId(category.getId());//设置categoryId article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);//刚提交的文章,其文章状态设为"审核中"; article.setCommentCounts(0);//文章初始评论数量设为0 article.setReadCounts(0);//文章初始阅读数量设为0 article.setIsDelete(YesOrNo.NO.type);//文章的"逻辑删除状态",设为0(即在article表中,代表未删除) article.setCreateTime(new Date());//设置创建时间 article.setUpdateTime(new Date());//设置更新时间 /** * 如果文章是定时发布的,就设置publish_time为预约发布时间; * 如果文章不是定时发布,而是立即发布,就设置publish_time为当前发布时间; */ if (article.getIsAppoint() == ArticleAppointType.TIMING.type) { article.setPublishTime(newArticleBO.getPublishTime()); } else { article.setPublishTime(new Date()); } //调用Dao层的方法,去插入 int res = articleMapper.insert(article); if (res != 1) {//如果插入失败,就抛出一个信息是"创建文章失败,请重试或联系管理员!"的自定义异常; GraceException.display(ResponseStatusEnum.ARTICLE_CREATE_ERROR); } } }说明:
(1) 看注释;
(2)在【common】通用工程中,创建了ArticleReviewStatus枚举类;来定义文章的审核状态;
(3)在【common】通用工程中,创建了YesOrNo枚举类;
(4)在【common】通用工程中,创建了ArticleAppointType枚举类;来表征文章是否是定时发布;
(5)如果设置属性方面,有不清楚的,看下article表,NewArticleBO类,Article类,就明白了;
利用【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;