首页功能实现
1、文章列表
1.1 接口说明
接口url:/articles
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
page | int | 当前页数 |
pageSize | int | 每页显示的数量 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
"summary": "通过Spring Boot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过`java -jar`命令就可以运行起来。\r\n\r\n这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。",
"commentCounts": 2,
"viewCounts": 54,
"weight": 1,
"createDate": "2609-06-26 15:58",
"author": "12",
"body": null,
"tags": [
{
"id": 5,
"avatar": null,
"tagName": "444"
},
{
"id": 7,
"avatar": null,
"tagName": "22"
},
{
"id": 8,
"avatar": null,
"tagName": "11"
}
],
"categorys": null
},
{
"id": 9,
"title": "Vue.js 是什么",
"summary": "Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。",
"commentCounts": 0,
"viewCounts": 3,
"weight": 0,
"createDate": "2609-06-27 11:25",
"author": "12",
"body": null,
"tags": [
{
"id": 7,
"avatar": null,
"tagName": "22"
}
],
"categorys": null
},
{
"id": 10,
"title": "Element相关",
"summary": "本节将介绍如何在项目中使用 Element。",
"commentCounts": 0,
"viewCounts": 3,
"weight": 0,
"createDate": "2609-06-27 11:25",
"author": "12",
"body": null,
"tags": [
{
"id": 5,
"avatar": null,
"tagName": "444"
},
{
"id": 6,
"avatar": null,
"tagName": "33"
},
{
"id": 7,
"avatar": null,
"tagName": "22"
},
{
"id": 8,
"avatar": null,
"tagName": "11"
}
],
"categorys": null
}
]
}
1.2 数据库表
CREATE TABLE `blog`.`ms_article` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`comment_counts` int(0) NULL DEFAULT NULL COMMENT '评论数量',
`create_date` bigint(0) NULL DEFAULT NULL COMMENT '创建时间',
`summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '简介',
`title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
`view_counts` int(0) NULL DEFAULT NULL COMMENT '浏览数量',
`weight` int(0) NOT NULL COMMENT '是否置顶',
`author_id` bigint(0) NULL DEFAULT NULL COMMENT '作者id',
`body_id` bigint(0) NULL DEFAULT NULL COMMENT '内容id',
`category_id` int(0) NULL DEFAULT NULL COMMENT '类别id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `blog`.`ms_tag` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`article_id` bigint(0) NOT NULL,
`tag_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE,
INDEX `tag_id`(`tag_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `blog`.`ms_sys_user` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
`admin` bit(1) NULL DEFAULT NULL COMMENT '是否管理员',
`avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
`create_date` bigint(0) NULL DEFAULT NULL COMMENT '注册时间',
`deleted` bit(1) NULL DEFAULT NULL COMMENT '是否删除',
`email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`last_login` bigint(0) NULL DEFAULT NULL COMMENT '最后登录时间',
`mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
`nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加密盐',
`status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
1.3 pojo类
在com.ling.blog.dao下新建pojo包,用于存放对应数据库表单的Javabean
package com.ling.blog.dao.pojo;
import lombok.Data;
@Data
public class Article {//文章类
public static final int Article_TOP = 1;
public static final int Article_Common = 0;
private Long id;
private String title;
private String summary;
private int commentCounts;
private int viewCounts;
/**
* 作者id
*/
private Long authorId;
/**
* 内容id
*/
private Long bodyId;
/**
*类别id
*/
private Long categoryId;
/**
* 置顶
*/
private int weight = Article_Common;
/**
* 创建时间
*/
private Long createDate;
}
package com.ling.blog.dao.pojo;
import lombok.Data;
@Data
public class SysUser {//用户类
private Long id;
private String account;
private Integer admin;
private String avatar;
private Long createDate;
private Integer deleted;
private String email;
private Long lastLogin;
private String mobilePhoneNumber;
private String nickname;
private String password;
private String salt;
private String status;
}
package com.ling.blog.dao.pojo;
import lombok.Data;
@Data
public class Tag {//标签类
private Long id;
private String avatar;
private String tagName;
}
1.4 controller
在com.ling.blog下新建 controller包,用于存放实现功能的控制器组件
package com.ling.blog.api;
import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.service.ArticleService;
import com.mszlu.blog.vo.Archive;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("articles")
public class ArticleController {
@Autowired
private ArticleService articleService;
//Result是统一结果返回
@PostMapping
public Result articles(@RequestBody PageParams pageParams) {
//ArticleVo 页面接收的数据
List<ArticleVo> articles = articleService.listArticlesPage(pageParams);
return Result.success(articles);
}
}
1.5 vo
在com.ling.blog下新建 vo包,用于存放需要返回的数据类型bean(并不是查询到的数据都要返回,因此将查到的数据注入vo包的特定bean中,要求什么数据,就创建对应的vo类)
Result: 用来存放返回前端的数据
package com.ling.blog.vo;
import com.ling.blog.dao.pojo.Article;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private boolean success;
private Integer code;
private String msg;
private Object data;
public static Result success(Object data) {
return new Result(true,200,"success",data);
}
public static Result fail(Integer code, String msg) {
return new Result(false,code,msg,null);
}
}
@Data
public class ArticleVo {//article对应的vo,用于展现给前端页面的信息
private Long id;
private String title;
private String summary;
private int commentCounts;
private int viewCounts;
private int weight;
/**
* 创建时间
*/
private String createDate;
private String author;
private ArticleBodyVo body;
private List<TagVo> tags;
private List<CategoryVo> categorys;
}
1.6 service
在com.ling.blog下新建service包,创建对应的service接口和实现类
public interface ArticleService {
List<ArticleVo> listArticlesPage(PageParams pageParams);
}
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private SysUserService sysUserService;
@Autowired
private TagsService tagsService;
public ArticleVo copy(Article article,boolean isAuthor,boolean isBody,boolean isTags){
ArticleVo articleVo = new ArticleVo();
BeanUtils.copyProperties(article,articleVo);
articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
if(isTag){
Long articleId = article.getId();
articleVo.setTags(tagService.findTagsByArticleId(articleId));
}
if(isAuthor){
Long authorId = article.getAuthorId();
articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
}
return articleVo;
}
private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) {
List<ArticleVo> articleVoList = new ArrayList<>();
for (Article article : records) {
articleVoList.add(copy(article,isTag,isAuthor));
}
return articleVoList;
}
/**
* 文章列表
* @param pageParams
* @return
*/
@Override
public Result listArticle(PageParams pageParams) {
/**
* 1、分页查询article数据库表
*/
Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//是否置顶排序
//再按创建时间排序
queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
List<Article> records = articlePage.getRecords();
List<ArticleVo> articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}
}
public interface SysUserService {
SysUser findUserById(Long id);
}
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser findUserById(Long id) {
SysUser sysUser = sysUserMapper.findUserById(id);
if(sysUser == null){
sysUser = new SysUser();
sysUser.setNickname("匿名用户");
}
return sysUser;
}
}
public interface TagService {
List<TagVo> findTagsByArticleId(Long articleId);
}
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
@Override
public List<TagVo> findTagsByArticleId(Long articleId) {
//mybatis-plus无法进行多表查询
List<Tag> tags = tagMapper.findTagsByArticleId(articleId);
return copyTagVoList(tags);
}
public List<TagVo> copyTagVoList(List<Tag> tags){
List<TagVo> tagVoList = new ArrayList<>();
for (Tag tag : tags) {
tagVoList.add(copyTagVo(tag));
}
return tagVoList;
}
public TagVo copyTagVo(Tag tag){
TagVo tagVo = new TagVo();
BeanUtils.copyProperties(tag,tagVo);
return tagVo;
}
}
1.7 dao
在com.ling.blog.dao下新建mapper包,对应的resource目录下也同样新建,要保持一致
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.Article;
import java.util.List;
public interface ArticleMapper extends BaseMapper<Article> {
}
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.Tag;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface TagMapper extends BaseMapper<Tag> {
/**
* 根据文章id查询标签列表
* @param articleId
* @return
*/
List<Tag> findTagsByArticleId(@Param("articleId") Long articleId);
}
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.SysUser;
import org.apache.ibatis.annotations.Param;
public interface SysUserMapper extends BaseMapper<SysUser> {
/**
* 跟剧作者id查询作者
* @param id
* @return
*/
SysUser findUserById(@Param("id") Long id);
}
对应的**mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.blog.dao.mapper.TagMapper">
<!--List<Tag> findTagsByArticleId(Long articleId);-->
<select id="findTagsByArticleId" parameterType="long" resultType="com.ling.blog.dao.pojo.Tag">
select id,avatar,tag_name as tagName
from ms_tag
where id in (select tag_id from ms_article_tag where article_id = #{articleId})
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.blog.dao.mapper.SysUserMapper">
<!--SysUser findUserById(@Param("id") Long id);-->
<select id="findUserById" parameterType="long" resultType="com.ling.blog.dao.pojo.SysUser">
select * from ms_sys_user where id = #{id}
</select>
</mapper>
在使用mybatis-plus自带的sql语句中,出现报错,因此自己在对应的mapper.xml文件中写了对应的sql语句。
2、文章标签
2.1 接口说明
接口url:/tags/hot
请求方式:GET
请求参数:无
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id":1,
"tagName":"4444"
}
]
}
2.2 controller
package com.ling.blog.controller;
import com.ling.blog.service.TagService;
import com.ling.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/tags")
public class TagsController {
@Autowired
private TagService tagService;
@GetMapping("/hot")
public Result hot(){
int limit = 6;
return tagService.hots(limit);
}
}
2.3 vo
package com.ling.blog.vo;
import lombok.Data;
@Data
public class TagVo {
private Long id;
private String tagName;
}
2.4 service
package com.ling.blog.service;
import com.ling.blog.vo.Result;
import com.ling.blog.vo.TagVo;
import java.util.List;
public interface TagService {
List<TagVo> findTagsByArticleId(Long articleId);
Result hots(int limit);
}
package com.ling.blog.service.impl;
import com.ling.blog.dao.mapper.TagMapper;
import com.ling.blog.dao.pojo.Tag;
import com.ling.blog.service.TagService;
import com.ling.blog.vo.Result;
import com.ling.blog.vo.TagVo;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
@Override
public List<TagVo> findTagsByArticleId(Long articleId) {
//mybatis-plus无法进行多表查询
List<Tag> tags = tagMapper.findTagsByArticleId(articleId);
return copyTagVoList(tags);
}
@Override
public Result hots(int limit) {
/**
* 1、标签所拥有的文章数量最多
* 2、查询,根据tag_id分组,计数,从大到小,取前limit个
*/
List<Long> tagIds = tagMapper.findHotsTagIds(limit);
if(CollectionUtils.isEmpty(tagIds)){
return Result.success(Collections.emptyList());
}
//需求的是tagId 和 tagName
List<Tag> tagList = tagMapper.findTagsByTagIds(tagIds);
return Result.success(tagList);
}
public List<TagVo> copyTagVoList(List<Tag> tags){
List<TagVo> tagVoList = new ArrayList<>();
for (Tag tag : tags) {
tagVoList.add(copyTagVo(tag));
}
return tagVoList;
}
public TagVo copyTagVo(Tag tag){
TagVo tagVo = new TagVo();
BeanUtils.copyProperties(tag,tagVo);
return tagVo;
}
}
2.5 dao
package com.ling.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ling.blog.dao.pojo.Tag;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface TagMapper extends BaseMapper<Tag> {
/**
* 根据文章id查询标签列表
* @param articleId
* @return
*/
List<Tag> findTagsByArticleId(@Param("articleId") Long articleId);
/**
* 查询最热标签前n条
* @param limit
* @return
*/
List<Long> findHotsTagIds(@Param("limit") int limit);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.blog.dao.mapper.TagMapper">
<!--List<Tag> findTagsByArticleId(Long articleId);-->
<select id="findTagsByArticleId" parameterType="long" resultType="com.ling.blog.dao.pojo.Tag">
select id,avatar,tag_name as tagName
from ms_tag
where id in (select tag_id from ms_article_tag where article_id = #{articleId})
</select>
<!--List<Long> findHotsTagIds(int limit);-->
<select id="findHotsTagIds" resultType="java.lang.Long">
select tag_id
from ms_article_tag
group by tag_id order by count(*) desc limit #{limit};
</select>
</mapper>
3、统一异常处理
不管是controller层还是service,dao层,都有可能报异常,如果是预料中的异常,可以直接捕获处理,如果是意料之外的异常,需要统一进行处理,进行记录,并给用户提示相对比较友好的信息。
package com.ling.blog.handler;
import com.ling.blog.vo.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice //对加了@Controller注解的方法进行拦截处理 AOP实现
public class AllExceptionHandler {
//进行异常处理,处理Exception.class异常
@ExceptionHandler(Exception.class)
@ResponseBody //返回json数据
public Result doException(Exception ex){
ex.printStackTrace();
return Result.fail(-999,"系统异常");
}
}
4、最热文章显示
4.1 接口说明
接口url:/articles/hot
请求方式:POST
请求参数:无
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
},
{
"id": 9,
"title": "Vue.js 是什么",
},
{
"id": 10,
"title": "Element相关",
}
]
}
4.2 controller
ArticleController:
/**
* 最热文章列表
* @return
*/
@PostMapping("/hot")
public Result hotArticle(){
int limit = 5;
return articleService.hotArticle(limit);
}
4.3 service
package com.ling.blog.service.impl下:ArticleServiceImpl
/**
* 最热文章
* @param limit
* @return
*/
@Override
public Result hotArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//select id,title from ms_article order by view_counts desc limit 5
queryWrapper.orderByDesc(Article::getViewCounts);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
5、最新文章显示
5.1 接口说明
接口url:/articles/new
请求方式:POST
请求参数:无
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"title": "springboot介绍以及入门案例",
},
{
"id": 9,
"title": "Vue.js 是什么",
},
{
"id": 10,
"title": "Element相关",
}
]
}
5.2 controller
/**
* 最新文章列表
* @return
*/
@PostMapping("/new")
public Result newArticle(){
int limit = 5;
return articleService.newArticle(limit);
}
5.3 Service
/**
* 最新文章
* @param limit
* @return
*/
@Override
public Result newArticle(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//select id,title from ms_article order by create_date desc limit 5
queryWrapper.orderByDesc(Article::getCreateDate);
queryWrapper.select(Article::getId,Article::getTitle);
queryWrapper.last("limit "+limit);
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(copyList(articles,false,false));
}
6、文章归档
6.1 接口说明
接口url:/articles/listArchives
请求方式:POST
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"year": "2021",
"month": "6",
"count": 2
}
]
}
老师给的: select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article group by year,month; 查不出年和月
需修改为以下形式:
select year(FROM_UNIXTIME(create_date/1000,’%Y-%m-%d %h:%i:%s’)) as year,month(FROM_UNIXTIME(create_date/1000,’%Y-%m-%d %h:%i:%s’)) as month,count(*) as count from ms_article group by year,month
6.2 controller
/**
* 文章归档
* @return
*/
@PostMapping("/listArchives")
public Result listArchives(){
return articleService.listArchives();
}
6.3 dos
由于传回的数据是数据库中没有的表结构,在com.ling.blog.dao下新建一个dos包存放
package com.ling.blog.dao.dos;
import lombok.Data;
@Data
public class Archives {
private Integer year;
private Integer month;
private Long count;
}
6.4 service
/**
* 文章归档
* @return
*/
Result listArchives();
/**
* 文章归档
*
* @return
*/
@Override
public Result listArchives() {
List<Archives> archivesList = articleMapper.listArchives();
return Result.success(archivesList);
}
6.5 dao
public interface ArticleMapper extends BaseMapper<Article> {
List<Archives> listArchives();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ling.blog.dao.mapper.ArticleMapper">
<!--List<Archives> listArchives();-->
<select id="listArchives" resultType="com.ling.blog.dao.dos.Archives">
select year(FROM_UNIXTIME(create_date/1000,'%Y-%m-%d %h:%i:%s')) as year,
month(FROM_UNIXTIME(create_date/1000,'%Y-%m-%d %h:%i:%s')) as month,
count(*) as count
from ms_article
group by year,month
</select>
</mapper>