1、创建项目
1.1、创建父工程
引入依赖版本管理
这里使用dependencyManagement进行依赖管理,如果在这里面的依赖爆红,这需要现将dependencyManagement去除后更新依赖再将其加入则不会产生爆红现象,因为被此包裹后,dependencyManagement并不会去加载里面的依赖,所以导致爆红现象。
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!-- springBoot的依赖配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- mybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<!-- easyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</dependencyManagement>
- Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
- jjwt是为了生成token。
- MP是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 阿里云OSS是为了做对象存储,在这里的用处是上传图片至阿里云并从中获取。
- easyExcel是为了增强导入导出excel,在poi基础上进行增强,可以进行大量操作。
- swagger、swagger-ui是为了生成文档接口,便于后端进行接口测试。
1.2、创建公共子模块
导入依赖
这里会发现所导入的依赖几乎没有版本依赖,是因为该子模块存在父工程模块,依赖版本都由父工程进行管理。这里同样是一个公共子模块,为了后续让其他模块对此模块进行一个引入,使用其中的依赖,而无需在自己模块中进行依赖引入。
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringSecurity启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- mybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里云OSS -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- easyExcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- swaggerUI -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
- lombok是java自动生成代码的插件。 它能提高开发效率,减少自己编写繁琐的代码,让代码看起来更整洁简略
- junit是为了进行单元测试。
- SpringSecurity是为了与SpringBoot做一个整合,形成一个安全模块,提供安全认证服务的框架,将认证和授权集一身。
- redis是为了作为应用程序与数据库之间的一层缓存。
- 代码生成器为了方便偷懒。。。
- mysql是为了连接数据源
1.3、创建前台博客模块
引入依赖
<dependencies>
<dependency>
<groupId>com.hashiqi</groupId>
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2、前台博客
2.1、准备工作
2.1.1、SpringBoot整合MP配置测试
创建代码生成器
这就是代码生成器模板,只需要改里面常用的部分就可以了,里面不常见的给了注释,常见的就没有标明注释了,请谅解。
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class CodeGet {
public static void main(String[] args) {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//gc.setOutputDir(projectPath + "/src/main/java");
gc.setOutputDir("F:\\ideaworkstation\\hashiqiBlog\\hashiqi-blog"+"/src/main/java");
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setAuthor("hashiqi");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/hashiqi-blog");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog"); //模块名
pc.setParent("com.hashiqi");
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("tb_article");
strategy.setTablePrefix("tb_");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BlogAppication {
public static void main(String[] args) {
SpringApplication.run(BlogAppication.class, args);
}
}
配置application.yml文件
server:
port: 7777
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/hashiqi_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
servlet:
multipart:
max-file-size: 5MB
max-request-size: 10MB
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: isDeleted
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
先导入文章表
DROP TABLE IF EXISTS `tb_article`;
CREATE TABLE `tb_article` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文章内容',
`summary` varchar(1023) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文章摘要',
`category_id` bigint(20) NULL DEFAULT NULL COMMENT '所属分类id',
`thumbnail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '缩略图',
`is_top` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '是否置顶(0否,1是)',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '状态(0已发布,1草稿)',
`view_count` bigint(20) NULL DEFAULT 0 COMMENT '访问量',
`is_comment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否允许评论 1是,0否',
`create_by` bigint(20) NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_by` bigint(20) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
`is_deleted` int(1) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文章表' ROW_FORMAT = Dynamic;
Artical实体类
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_article")
public class Article implements Serializable {
private static final long serialVersionUID=1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 标题
*/
private String title;
/**
* 文章内容
*/
private String content;
/**
* 文章摘要
*/
private String summary;
/**
* 所属分类id
*/
private Long categoryId;
/**
* 缩率图
*/
private String thumbnail;
/**
* 是否置顶(1:是,0:否)
*/
private String isTop;
/**
* 状态(0:已发布;1:草稿)
*/
private String status;
/**
* 访问量
*/
private Long viewCount;
/**
* 是否允许评论(0:否;1:是)
*/
private String isComment;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 最后更新者
*/
private Long updateBy;
/**
* 最后更新时间
*/
private LocalDateTime updateTime;
/**
* 是否已删除(0:未删除;1:已删除)
*/
private Integer isDeleted;
}
业务层及业务实现层
import com.hashiqi.domain.entity.Article;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ArticleService extends IService<Article> {
}
import com.hashiqi.domain.entity.Article;
import com.hashiqi.mapper.ArticleMapper;
import com.hashiqi.service.ArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
}
mapper层及xml
import com.hashiqi.domain.entity.Article;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ArticleMapper extends BaseMapper<Article> {
}
<?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.hashiqi.mapper.ArticleMapper">
</mapper>
2.2、热门文章列表
2.2.1、文章表分析
通过需求去分析需要有哪些字段。
2.2.2、需求分析
需要查询浏览量最高的前10篇文章的信息。要求展示文章标题和浏览量。把能让用户自己点击跳转到具体的文章详情进行浏览。
注意:不能把草稿展示出来,不能把删除了的文章查询出来。要按照浏览量进行降序排序。
2.2.3、接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /article/hotArticleList | 暂不需要token请求头 |
2.2.4、基础版本代码实现
准备工作
统一结果返回集【三部曲:状态码、消息、数据】
import com.fasterxml.jackson.annotation.JsonInclude;
import com.hashiqi.enums.AppHttpCodeEnum;
import java.io.Serializable;
@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;
}
}
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, "必需填写用户名"),
LOGIN_ERROR(505,"用户名或密码错误");
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;
}
}
代码实现
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.ArticleService;
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("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
// 查询热门文章
@GetMapping("/hotArticleList")
public ResponseResult hotArticleList(){
ResponseResult result = articleService.hotArticleList();
return result;
}
}
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Article;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ArticleService extends IService<Article> {
// 查询热门文章
ResponseResult hotArticleList();
}
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Article;
import com.hashiqi.mapper.ArticleMapper;
import com.hashiqi.service.ArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
/**
* 获取热点文章
* @return 返回热门文章
*/
@Override
public ResponseResult hotArticleList() {
// 查询热门文章【已发布、最多10条】,封装成ResponseResult
return ResponseResult.okResult(
this.page(new Page<>(1, 10),
new LambdaQueryWrapper<Article>()
.eq(Article::getStatus, 0)
.orderByDesc(Article::getViewCount)).getRecords());
}
}
解决跨域问题:放在公共子模块
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@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);
}
}
上面使用的是直接获取整个文章实体的数据,但是显示在前端的数据并不是要全部,这样会造成传出不必要的字段信息,而且实体类中还存在着一些比如逻辑删除标志等一些字段,这些都是需要去处理的,所以接来是对其进行一个小优化。
2.2.5、使用VO进行优化
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HotArticleVO {
// 文章Id
private Long id;
// 标题
private String title;
// 浏览量
private Long viewCount;
}
使用BeanUtils
使用该工具是为了使得从实体类转向像VO、DTO、PO这样的就会比较方便,而不是通过set、get进行一个个的取值赋值。
还有就是虽然说这样方便了,但是对于集合类整体转换还是不太方便,所以下面会将前者进行一个封装,创建一个方法进行使用。
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
/**
* 获取热点文章
* @return 返回热门文章
*/
@Override
public ResponseResult hotArticleList() {
// 查询热门文章【已发布、最多10条】,封装成ResponseResult
List<Article> articles = this.page(new Page<>(1, 10),
new LambdaQueryWrapper<Article>()
.eq(Article::getStatus, 0)
.orderByDesc(Article::getViewCount)).getRecords();
// 通过BeanUtils将Article封装成HotArticleVO
List<HotArticleVO> hotArticleList = new ArrayList<>();
articles.stream().forEach(article -> {
HotArticleVO hotArticleVO = new HotArticleVO();
BeanUtils.copyProperties(article, hotArticleVO);
hotArticleList.add(hotArticleVO);
});
return ResponseResult.okResult(hotArticleList);
}
}
2.2.6、字面值处理
实际项目中都不允许直接在代码中使用字面值。都需要定义成常量来使用。这种方式有利于提高代码的可维护性。
public class SystemConstants
{
/**
* 文章是草稿
*/
public static final int ARTICLE_STATUS_DRAFT = 1;
/**
* 文章是正常分布状态
*/
public static final int ARTICLE_STATUS_NORMAL = 0;
}
.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL)
2.3、Bean拷贝工具类封装
<V>指的是该方法为泛型方法,第二个V指的是返回类型为V。这里使用了反射。
import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.stream.Collectors;
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());
}
}
对之前获取文章列表的接口进行优化
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
/**
* 获取热点文章
* @return 返回热门文章
*/
@Override
public ResponseResult hotArticleList() {
// 查询热门文章【已发布、最多10条】,封装成ResponseResult
List<Article> articles = this.page(new Page<>(1, 10),
new LambdaQueryWrapper<Article>()
.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL)
.orderByDesc(Article::getViewCount)).getRecords();
// 通过BeanUtils将Article封装成HotArticleVO
return ResponseResult.okResult(BeanCopyUtils.copyBeanList(articles, HotArticleVO.class));
}
}
2.4、查询分类列表
2.4.1、分类表分析
通过需求去分析需要有哪些字段。
DROP TABLE IF EXISTS `tb_category`;
CREATE TABLE `tb_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(127) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类名',
`pid` bigint(20) NULL DEFAULT -1 COMMENT '父分类id,如果没有父分类为-1',
`description` varchar(511) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态0:正常,1禁用',
`create_by` bigint(20) NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_by` bigint(20) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
`is_deleted` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '分类表' ROW_FORMAT = Dynamic;
2.4.2、需求分析
页面上需要展示分类列表,用户可以点击具体的分类查看该分类下的文章列表。
注意: 要求只展示有发布正式文章的分类;必须是正常状态的分类
2.4.3、接口设计
2.4.4、EasyCode代码模板
可以进行参考,但上面和这里使用了MP代码生成器,主要还是用习惯了。
##导入宏定义
$!{define.vm}
##保存文件(宏定义)
#save("/entity", ".java")
##包路径(宏定义)
#setPackageSuffix("entity")
##自动导入包(全局变量)
$!{autoImport.vm}
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
##表注释(宏定义)
#tableComment("表实体类")
@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("$!{tableInfo.obj.name}")
public class $!{tableInfo.name} {
#foreach($column in $tableInfo.pkColumn)
#if(${column.comment})//${column.comment}#end
@TableId
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
#foreach($column in $tableInfo.otherColumn)
#if(${column.comment})//${column.comment}#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
}
##导入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("Mapper")
##保存文件(宏定义)
#save("/mapper", "Mapper.java")
##包路径(宏定义)
#setPackageSuffix("mapper")
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
##表注释(宏定义)
#tableComment("表数据库访问层")
public interface $!{tableName} extends BaseMapper<$!tableInfo.name> {
}
##导入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("Service")
##保存文件(宏定义)
#save("/service", "Service.java")
##包路径(宏定义)
#setPackageSuffix("service")
import com.baomidou.mybatisplus.extension.service.IService;
##表注释(宏定义)
#tableComment("表服务接口")
public interface $!{tableName} extends IService<$!tableInfo.name> {
}
##导入宏定义
$!{define.vm}
##设置表后缀(宏定义)
#setTableSuffix("ServiceImpl")
##保存文件(宏定义)
#save("/service/impl", "ServiceImpl.java")
##包路径(宏定义)
#setPackageSuffix("service.impl")
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
##表注释(宏定义)
#tableComment("表服务实现类")
@Service("$!tool.firstLowerCase($tableInfo.name)Service")
public class $!{tableName} extends ServiceImpl<$!{tableInfo.name}Mapper, $!{tableInfo.name}> implements $!{tableInfo.name}Service {
}
使用mp代码生成器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class CodeGet {
public static void main(String[] args) {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//gc.setOutputDir(projectPath + "/src/main/java");
gc.setOutputDir("F:\\ideaworkstation\\hashiqiBlog\\hashiqi-blog"+"/src/main/java");
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setAuthor("hashiqi");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/hashiqi_blog");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog"); //模块名
pc.setParent("com.hashiqi");
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("tb_article");
strategy.setTablePrefix("tb_");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
2.4.5、代码实现
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.CategoryService;
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("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
// 获取所有分类列表
@GetMapping("/getCategoryList")
public ResponseResult getCategoryList() {
return categoryService.getCategoryList();
}
}
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Category;
import com.baomidou.mybatisplus.extension.service.IService;
public interface CategoryService extends IService<Category> {
// 获取所有分类列表
ResponseResult getCategoryList();
}
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hashiqi.constants.SystemConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Article;
import com.hashiqi.domain.entity.Category;
import com.hashiqi.domain.vo.CategoryVO;
import com.hashiqi.mapper.CategoryMapper;
import com.hashiqi.service.ArticleService;
import com.hashiqi.service.CategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hashiqi.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private ArticleService articleService;
/**
* 获取所有分类列表
* @return 返回分类列表
*/
@Override
public ResponseResult getCategoryList() {
// 查询所有已发布文章
List<Article> articleList = articleService.list(new LambdaQueryWrapper<Article>().eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL));
// 获取文章的分类id并进行去重
Set<Long> categoryIds = articleList.stream().map(Article::getCategoryId).collect(Collectors.toSet());
// 查询分类表
List<Category> categories = this.listByIds(categoryIds).stream().filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus())).collect(Collectors.toList());
// 将Category封装成CategoryVO
List<CategoryVO> categoryVOs = BeanCopyUtils.copyBeanList(categories, CategoryVO.class);
return ResponseResult.okResult(categoryVOs);
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CategoryVO {
private Long id;
private String name;
}
2.5、分页查询文章列表
2.5.1、需求分析
在首页和分类页面都需要查询文章列表。
首页:查询所有的文章
分类页面:查询对应分类下的文章
要求:只能查询正式发布的文章;置顶的文章要显示在最前面
2.5.2、接口设计
2.5.3、代码实现
MP支持分页配置
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
Article Controller
// 获取文章列表
@GetMapping("/articleList")
public ResponseResult articleList(
Integer pageNum,
Integer pageSize,
Long categoryId) {
return articleService.articleList(pageNum, pageSize, categoryId);
}
Article Service
// 获取文章列表
ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId);
vo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ArticleVO {
/**
* 主键
*/
private Long id;
/**
* 标题
*/
private String title;
/**
* 文章摘要
*/
private String summary;
/**
* 所属分类id
*/
private String categoryName;
/**
* 缩率图
*/
private String thumbnail;
/**
* 访问量
*/
private Long viewCount;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageVO {
private List rows;
private Long total;
}
Article ServiceImpl
/**
* 获取文章列表
* @param pageNum 当前页
* @param pageSize 页大小
* @param categoryId 分类id
* @return
*/
@Override
public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {
// 查询条件
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
// 判断有无分类id,如果有则需根据分类id查询
wrapper.eq(Objects.nonNull(categoryId)&&categoryId > 0, Article::getCategoryId, categoryId)
// 状态为已发布
.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL)
// 根据isTop进行降序排序
.orderByDesc(Article::getIsTop);
List<Article> articles = this.page(new Page<>(pageNum, pageSize), wrapper).getRecords();
// 通过文章查询对应的分类名, 在文章实体中加入分类名称【使用@TableField】
articles.stream().map(
article -> article.setCategoryName(categoryService.getById(article.getCategoryId()).getName())
).collect(Collectors.toList());
// 将Article封装成ArticleVO
List<ArticleVO> articleVOs = BeanCopyUtils.copyBeanList(articles, ArticleVO.class);
return ResponseResult.okResult(new PageVO(articleVOs, (long) articleVOs.size()));
}
2.5.4、FastJson配置
WebConfig
这里是用于前后端之间的时间格式转换。
@Bean//使用@Bean注入fastJsonHttpMessageConvert
public HttpMessageConverter fastJsonHttpMessageConverters() {
//1.需要定义一个Convert转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);
fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(fastJsonHttpMessageConverters());
}
2.6、文章详情接口
2.6.1、需求分析
要求在文章列表点击阅读全文时能够跳转到文章详情页面,可以让用户阅读文章正文。
要求:①要在文章详情中展示其分类名
2.6.2、接口设计
请求方式 | 请求路径 |
---|---|
Get | /article/{id} |
响应格式:
{
"code": 200,
"data": {
"categoryId": "1",
"categoryName": "java",
"content": "内容",
"createTime": "2022-01-23 23:20:11",
"id": "1",
"isComment": "0",
"title": "xxx",
"viewCount": "114"
},
"msg": "操作成功"
}
2.6.3、代码实现
Article Controller
// 通过文章id获取文章详情
@GetMapping("/{id}")
public ResponseResult getArticleDetail(@PathVariable("id") Long id) {
return articleService.getArticleDetail(id);
}
Article Service
// 通过文章id获取文章详情
ResponseResult getArticleDetail(Long id);
Article ServiceImpl
/**
* 通过文章id获取文章详情
* @param id 文章id
* @return 文章详情
*/
@Override
public ResponseResult getArticleDetail(Long id) {
// 根据文章id获取文章,并判断是否存在
Article article = this.getById(id);
if (null == article) {
throw new RuntimeException("文章不存在");
}
// 将Article封装成ArticleDetailVO
ArticleDetailVO articleDetailVO = BeanCopyUtils.copyBean(article, ArticleDetailVO.class);
//根据分类id查询分类名
Category category = categoryService.getById(articleDetailVO.getCategoryId());
if (null == category) {
throw new RuntimeException("分类不存在");
}
articleDetailVO.setCategoryName(category.getName());
return ResponseResult.okResult(articleDetailVO);
}
vo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ArticleDetailVO {
/**
* 主键
*/
private Long id;
/**
* 标题
*/
private String title;
/**
* 文章内容
*/
private String content;
/**
* 文章摘要
*/
private String summary;
/**
* 所属分类id
*/
private Long categoryId;
/**
* 所属分类名称
*/
private String categoryName;
/**
* 缩率图
*/
private String thumbnail;
/**
* 访问量
*/
private Long viewCount;
/**
* 创建时间
*/
private Date createTime;
}
2.7、网站链接查询
2.7.1、网站链接表分析
通过需求去分析需要有哪些字段。
DROP TABLE IF EXISTS `tb_link`;
CREATE TABLE `tb_link` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`logo` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`description` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '网站地址',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '2' COMMENT '审核状态 (0代表审核通过,1代表审核未通过,2代表未审核)',
`create_by` bigint(20) NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_by` bigint(20) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT NULL,
`is_deleted` int(1) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '网链' ROW_FORMAT = Dynamic;
2.7.2、需求分析
在友链页面要查询出所有的审核通过的友链。
2.7.3、接口设计
请求方式 | 请求路径 |
---|---|
Get | /link/getAllLink |
响应格式:
{
"code": 200,
"data": [
{
"address": "https://www.baidu.com",
"description": "sda",
"id": "1",
"logo": "图片url1",
"name": "sda"
},
{
"address": "https://www.qq.com",
"description": "dada",
"id": "2",
"logo": "图片url2",
"name": "sda"
}
],
"msg": "操作成功"
}
2.7.4、代码实现
Link
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_link")
public class Link implements Serializable {
private static final long serialVersionUID=1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String logo;
private String description;
/**
* 网站地址
*/
private String address;
/**
* 审核状态 (0代表审核通过,1代表审核未通过,2代表未审核)
*/
private String status;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer isDeleted;
}
Link Controller
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.service.LinkService;
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("/link")
public class LinkController {
@Autowired
private LinkService linkService;
// 获取所有网链
@GetMapping("/getAllLink")
public ResponseResult getAllLink() {
return linkService.getAllLink();
}
}
Link Service
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Link;
import com.baomidou.mybatisplus.extension.service.IService;
public interface LinkService extends IService<Link> {
// 获取所有网链
ResponseResult getAllLink();
}
Link ServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hashiqi.constants.SystemConstants;
import com.hashiqi.domain.ResponseResult;
import com.hashiqi.domain.entity.Link;
import com.hashiqi.domain.vo.LinkVO;
import com.hashiqi.mapper.LinkMapper;
import com.hashiqi.service.LinkService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hashiqi.utils.BeanCopyUtils;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {
/**
* 获取所有网链
* @return 返回所有网链
*/
@Override
public ResponseResult getAllLink() {
// 查询所有审核过的网链
List<Link> links = this.list(new LambdaQueryWrapper<Link>().
eq(Link::getStatus, SystemConstants.LINK_STATUS_NORMAL)
.orderByAsc(Link::getCreateTime));
// 将Link转成LinkVO
List<LinkVO> linkVOs = BeanCopyUtils.copyBeanList(links, LinkVO.class);
return ResponseResult.okResult(linkVOs);
}
}
vo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LinkVO {
private Long id;
private String name;
private String logo;
private String description;
/**
* 网站地址
*/
private String address;
}
System Constants
/**
* 网链状态为审核通过
*/
public static final String LINK_STATUS_NORMAL = "0";
/**
* 网链状态为审核未通过
*/
public static final String LINK_STATUS_FORBID = "1";
/**
* 网链状态为未审核
*/
public static final String LINK_STATUS_NOAUDIT = "2";
2.8、前台登录功能实现
前台和后台的认证授权统一都使用SpringSecurity安全框架来实现。
2.8.1、需求分析
需要实现登录功能
有些功能必须登录后才能使用,未登录状态是不能使用的。
2.8.2、接口设计
请求方式 | 请求路径 |
---|---|
POST | /login |
请求体:
{
"userName":"hashiqi",
"password":"123"
}
响应格式:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0ODBmOThmYmJkNmI0NjM0OWUyZjY2NTM0NGNjZWY2NSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY0Mzg3NDMxNiwiZXhwIjoxNjQzOTYwNzE2fQ.ldLBUvNIxQCGemkCoMgT_0YsjsWndTg5tqfJb77pabk",
"userInfo": {
"avatar": "https://xxx/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2F3bf9c263bc0f2ac5c3a7feb9e218d07475573ec8.gi",
"email": "xxx@qq.com",
"id": 1,
"nickName": "xxx",
"sex": "1"
}
},
"msg": "操作成功"
}
2.8.3、用户表分析
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',
`nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',
`type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户类型:0代表普通用户,1代表管理员',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
`email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
`avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
`create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建人的用户id',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新人',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
`is_deleted` int(11) NULL DEFAULT 0 COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14787164048666 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
2.8.4、思路分析
登录
-
自定义登录接口
-
调用ProviderManager的方法进行认证 如果认证通过生成jwt
-
把用户信息存入redis中
-
-
自定义UserDetailsService
-
在这个实现类中去查询数据库
-
注意配置passwordEncoder为BCryptPasswordEncoder。
检验
-
定义Jwt认证过滤器
-
获取token
-
解析token获取其中的userId
-
从redis中获取用户信息
-
存入SecurityContextHolder
-
2.8.5、准备工作
User
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
public class User implements Serializable {
private static final long serialVersionUID=1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 用户类型:0代表普通用户,1代表管理员
*/
private String type;
/**
* 账号状态(0正常 1停用)
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phonenumber;
/**
* 用户性别(0男,1女,2未知)
*/
private String sex;
/**
* 头像
*/
private String avatar;
/**
* 创建人的用户id
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer isDeleted;
}
添加依赖
因为是要进行有关Security的操作,所以要导入Security依赖,前面在创建工程的时候已经导入了。
<!-- SpringSecurity启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
工具类和相关配置类
config包
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
utils包
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "hashiqi";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("hashiqi") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
Claims claims = parseJWT(token);
System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class WebUtils {
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void setDownLoadHeader(String filename, ServletContext context, HttpServletResponse response) throws UnsupportedEncodingException {
String mimeType = context.getMimeType(filename);//获取文件的mime类型
response.setHeader("content-type",mimeType);
String fname= URLEncoder.encode(filename,"UTF-8");
response.setHeader("Content-disposition","attachment; filename="+fname);
// response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// response.setCharacterEncoding("utf-8");
}
}
先到这里~~,敬请期待,第二部分请点击以下链接即可。