热门文章列表

HeJin大约 5 分钟项目实战前后端分离博客项目

需求分析

需要查询浏览量最高的前10篇文章的信息。要求展示文章标题浏览量。把能让用户自己点击跳转到具体的文章详情进行浏览。

​ 注意:不能把草稿展示出来,不能把删除了的文章查询出来。要按照浏览量进行降序排序

接口设计

  • 请求地址:/article/hotArticleList

  • 请求方式:GET

  • 不需要请求参数。

  • 响应格式:

    {
        "code": 200,
        "data": [
            {
                "id": "1004",
                "title": "哔哩哔哩",
                "viewCount": "2233"
            },
            {
                "id": "1000",
                "title": "SpringSecurity从入门到精通",
                "viewCount": "118"
            },
            {
                "id": "1003",
                "title": "sdad",
                "viewCount": "44"
            }
        ],
        "msg": "操作成功"
    }
    

基础版本代码实现

统一响应格式和枚举响应码

这些是通用的,放在公共模块sanfen-framework

统一响应类

package com.sanfen.domain;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
    private Integer code;
    private String msg;
    private T data;

    public ResponseResult() {
        this.code = AppHttpCodeEnum.SUCCESS.getCode();
        this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.error(code, msg);
    }
    public static ResponseResult okResult() {
        ResponseResult result = new ResponseResult();
        return result;
    }
    public static ResponseResult okResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.ok(code, null, msg);
    }

    public static ResponseResult okResult(Object data) {
        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
        if(data!=null) {
            result.setData(data);
        }
        return result;
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums){
        return setAppHttpCodeEnum(enums,enums.getMsg());
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
        return setAppHttpCodeEnum(enums,msg);
    }

    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
        return okResult(enums.getCode(),enums.getMsg());
    }

    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
        return okResult(enums.getCode(),msg);
    }

    public ResponseResult<?> error(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
        this.code = code;
        this.data = data;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
        return this;
    }

    public ResponseResult<?> ok(T data) {
        this.data = data;
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

响应码枚举类

package com.sanfen.enums;

public enum AppHttpCodeEnum {
    // 成功
    SUCCESS(200,"操作成功"),
    // 登录
    NEED_LOGIN(401,"需要登录后操作"),
    NO_OPERATOR_AUTH(403,"无权限操作"),
    SYSTEM_ERROR(500,"出现错误"),
    USERNAME_EXIST(501,"用户名已存在"),
    PHONENUMBER_EXIST(502,"手机号已存在"),
    EMAIL_EXIST(503, "邮箱已存在"),
    REQUIRE_USERNAME(504, "必需填写用户名"),
    REQUIRE_NICKNAME(508, "必需填写昵称"),
    REQUIRE_EMAIL(509, "必需填写邮箱"),
    REQUIRE_PASSWORD(510, "必需填写密码"),
    LOGIN_ERROR(505,"用户名或密码错误"),
    CONTENT_NOT_NULL(506, "评论内容不能为空"),
    FILE_TYPE_ERROR(507,"文件类型错误, 请上传png图片"),
    NICKNAME_EXIST(511, "昵称已存在");

    int code;
    String msg;

    AppHttpCodeEnum(int code, String errorMessage){
        this.code = code;
        this.msg = errorMessage;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

热门文章接口编写

controller

@RestController
@RequestMapping("/article")
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    /**
     * 查询浏览量前10的文章
     * @return 结果
     */
    @GetMapping("/hotArticleList")
    public ResponseResult hotArticleList(){
        return articleService.hotArticleList();
    }

}

service

@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Override
    public ResponseResult hotArticleList() {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        // 必须是正式文章
        queryWrapper.eq(Article::getStatus, 0);
        // 按浏览量降序
        queryWrapper.orderByDesc(Article::getViewCount);
        // 最多只查询10条
        Page<Article> page = new Page<>(1, 10);
        page(page);

        List<Article> articles = page.getRecords();

        return ResponseResult.okResult(articles);
    }

}

测试:访问http://localhost:7777/article/hotArticleList

image-20220729160227857
image-20220729160227857

可以发现返回数据是文章表的全部字段,但是热门文章不需要这么多。我们可以使用VO进行优化,每个接口对应一个VO。

解决跨域问题

与VUE项目进行联调的时候,会出现跨域。所以我们需要配置一下。配置类博客前台和后台都会用,放到公共模块sanfen-framework

package com.hejin.config;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }

}

使用VO优化

​ 目前我们的响应格式其实是不符合接口文档的标准的,多返回了很多字段。这是因为我们查询出来的结果是Article来封装的,Article中字段比较多。

​ 我们在项目中一般最后还要把VO来接受查询出来的结果。一个接口对应一个VO,这样即使接口响应字段要修改也只要改VO即可

bean拷贝工具类

使用VO的时候涉及到bean的拷贝,使用工具类:

package com.hejin.util;

public class BeanCopyUtils {

    private BeanCopyUtils() {
    }

    public static <V> V copyBean(Object source,Class<V> clazz) {
        //创建目标对象
        V result = null;
        try {
            result = clazz.newInstance();
            //实现属性copy
            BeanUtils.copyProperties(source, result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回结果
        return result;
    }
    public static <O,V> List<V> copyBeanList(List<O> list, Class<V> clazz){
        return list.stream()
                .map(o -> copyBean(o, clazz))
                .collect(Collectors.toList());
    }
}

字面量处理

对于字面值,比如前10的热门文章。这个10一般使用静态常量来定义,比较好维护。不建议直接写在代码里。

package com.hejin.constants;

public class SystemConstants
{
    /**
     *  文章是草稿
     */
    public static final int ARTICLE_STATUS_DRAFT = 1;
    /**
     *  文章是正常分布状态
     */
    public static final int ARTICLE_STATUS_NORMAL = 0;
    
}

新建HotArticleVo

sanfen-framework新建HotArticleVo类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HotArticleVo {

    private Long id;

    /**
     * 标题
     */
    private String title;

    /**
     * 访问量
     */
    private Long viewCount;
    
    /**
     *  热门文章页码
     */
    public static final int HOT_ARTICLE_PAGE = 1;

    /**
     *  热门文章数量
     */
    public static final int HOT_ARTICLE_SIZE = 10;

}

sanfen-framework此时的项目结构:

image-20220729161158703
image-20220729161158703

修改ArticleServiceImpl

@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Override
    public ResponseResult hotArticleList() {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        // 必须是正式文章
        queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
        // 按浏览量降序
        queryWrapper.orderByDesc(Article::getViewCount);
        // 最多只查询10条
        Page<Article> page = new Page<>(SystemConstants.HOT_ARTICLE_PAGE, SystemConstants.HOT_ARTICLE_SIZE);
        page(page);
        List<Article> articles = page.getRecords();

        // bean拷贝
        List<HotArticleVo> hotArticleVos = BeanCopyUtils.copyBeanList(articles, HotArticleVo.class);

        return ResponseResult.okResult(hotArticleVos);
    }

}

测试访问:http://localhost:7777/article/hotArticleList

image-20220729162603948
image-20220729162603948