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接口,定义【发表文章,接口】;

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;然后,前端在调用本篇博客的【发表文章,接口】时,这些图片的地址会传送到后端;

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

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()) {
            Map<String, String> map = 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<Category>;
            List<Category> list = 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层的内容,在三部分中,有介绍;


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

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

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

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);
}

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

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类,就明白了;


附加一:利用【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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值