首先,本项目是模仿的B站的Up主MarkHub的博客系统,视频中详细得对每一个文件的作用都做了解释。在本篇文章中不会进行过多赘述,只是专注于项目的搭建。
视频链接请戳下方
https://www.bilibili.com/video/BV1PQ4y1P7hZ?from=search&seid=1550841645384365421
前端搭建传送门
项目演示效果:www.markerhub.com:8084/blogs
后端SpringBoot服务器搭建
后端使用的技术栈有:
技术栈:
- SpringBoot
- mybatis plus
- shiro
- lombok
- redis
- hibernate validatior
- jwt
首先使用IDEA初始化一个SpringBoot项目,选择的依赖有如下
DevTools为开发热部署,修改代码时,按Ctrl + F9可重启服务器。
Lombok用于简化代码,使用注解代替get、set、构造方法,让代码更优雅。
这两个依赖也要选上,数据库驱动使用的是MySQL。
整合mybatis plus
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mp代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
将以上代码放入pom.xml文件中以整合mybatis
接下来创建两张表m_blog和m_user,m_blog用于存放博客信息,m_user用于存放用户信息
CREATE TABLE
m_user
(id
bigint(20) NOT NULL AUTO_INCREMENT,
username
varchar(64) DEFAULT NULL,avatar
varchar(255) DEFAULT
NULL,password
varchar(64)
DEFAULT NULL,status
int(5) NOT NULL,created
datetime DEFAULT
NULL,last_login
datetime DEFAULT NULL, PRIMARY KEY (id
),
KEYUK_USERNAME
(username
) USING BTREE ) ENGINE=InnoDB DEFAULT
CHARSET=utf8; CREATE TABLEm_blog
(id
bigint(20) NOT NULL
AUTO_INCREMENT,user_id
bigint(20) NOT NULL,title
varchar(255) NOT NULL,description
varchar(255) NOT NULL,
content
longtext,created
datetime NOT NULL ON UPDATE
CURRENT_TIMESTAMP,status
tinyint(4) DEFAULT NULL, PRIMARY KEY
(id
) ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
INSERT INTOvueblog
.m_user
(id
,username
,avatar
,
password
,status
,created
,last_login
) VALUES (‘1’,
‘markerhub’,
‘https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg’,
NULL, ‘96e79218965eb72c92a549dd5a330112’, ‘0’, ‘2020-04-20 10:44:01’,
NULL);
这边数据库和表创建好了以后,在application.yml文件中添加配置信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
现在可以测试启动一下数据库连接是否正常,如果正常下一步就可以使用CodeGenerator生成代码。该类文件可在 https://github.com/Alenjoke/vue-master 下载
运行该文件之前,需要配置数据库相关的连接。
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("com.project.blog.markerhub");
配置完成后,运行该文件,并在控制台输入表名m_user, m_blog
请输入表名,多个英文逗号分割:
m_user, m_blog
运行后生成以下文件
可以看到开发业务的Controller层,Dao层,Service层都生成好了,非常方便。
生成了相关文件以后,还要配置一个包扫描器,否则项目会启动失败,这里我在com.project.blog.config下增加一个类:
@Configuration
@EnableTransactionManagement
@MapperScan("com.project.blog.mapper")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
好了,目前为止,各层的访问已经能够形成一个闭环,可以开始业务逻辑的开发。
对于前后端分离项目,数据的封装是必要的,因为除了数据,前端还需要了解数据的状态,提示信息等。
在com.project.blog.common.lang包中生成一个Result类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result implements Serializable {
private int code;
private String msg;
private Object data;
public static Result status(int code, String msg, Object data) {
return new Result(code, msg, data);
}
public static Result success(Object obj) {
return new Result(200, "操作成功", obj);
}
public static Result fail(Object obj) {
return new Result(400, "操作失败", obj);
}
}
这里我做了一个简单的封装。
整合shiro+jwt
shiro这块的官方文档如下
https://github.com/alexxiyang/shiro-redis/blob/master/docs/README.md#spring-boot-starter
shiro + jwt的主要作用是在请求链接到达controller层以前进行一个过滤,判断用户是否带有token,对请求进行一个角色以及权限认证。shiro这块深入了解的话也需要花费很长时间,本篇文章专注于框架的搭建,对于shiro如何对用户权限的认证就不做过多赘述。
首先添加相关依赖
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis-spring-boot-starter</artifactId>
<version>3.2.1</version>
</dependency>
<!-- hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
添加以下文件,可在https://github.com/Alenjoke/vue-master 下载
com.project.blog.config.ShiroConfig;
com.project.blog.shiro.AccountProfile;
com.project.blog.shiro.AccountRealm;
com.project.blog.shiro.JwtFilter;
com.project.blog.shiro.JwtToken;
com.markerhub.util.JwtUtils;
com.markerhub.util.ShiroUtil;
JwtUtils用了配置文件注解,需要添加依赖
<!-- 配置注解器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
application.yml增加以下配置
shiro-redis:
enabled: true
redis-manager:
host: 127.0.0.1:6379
markerhub:
jwt:
# 加密秘钥
secret: f4e2e52034348f86b67cde581c0f9eb5
# token有效时长,7天,单位秒
expire: 604800
header: token
除此以外
新增文件resources/META-INF/spring-devtools.properties,增加以下配置,否则devtool重新启动时会报错
restart.include.shiro-redis=/shiro-[\\w-\\.]+jar
异常处理
shiro配置好了以后,进行异常的配置处理,创建一个异常捕获类,用于捕获后台出现的所有异常,前端每次发起请求时,如果出现了异常,返回对应的异常信息进行处理,这样浏览器不会出现500的页面错误请求。
创建类 com.markerhub.common.lang.Exception.GlobalExceptionHandler
类名上增加ControllerAdvice注解表示全局处理异常
这里我使用了@RestControllerAdvice它整合了ControllerAdvice和RestController注解
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕获异常
* @param e
* @return
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(value = RuntimeException.class)
public Result handler(RuntimeException e) {
log.error("运行时异常============");
return Result.fail(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ShiroException.class)
public Result handler(ShiroException e) {
log.error("运行时异常============");
return Result.status(401, e.getMessage(), null);
}
当我们的程序抛出一个异常时,就会被这个类捕获,根据ExceptionHandler注解中传入的异常类进行匹配,进入到对应的handler中处理。
实体校验
在实体类上增加以下注解
@NotBlank(message = "昵称不能为空")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
然后在 Controller的参数中添加Valid注解即可验证
@PostMapping("/save")
public Result save(@Validated @RequestBody User user) {
return Result.success(user);
}
验证失败时,会抛出异常,转到全局异常捕获类中,从而将错误信息传给前端。
注意,我之前使用的spring-boot-starter-parent为2.4.4版本,找不到验证的包,后换成2.2.6.RELEASE版本
跨域处理
前后端分离项目必然面对的一个问题,处理代码如下
/**
* 解决跨域问题
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
除此以外,jwt的拦截器也需要配置跨域
/**
* 拦截器的跨域处理
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
如此,前后端分离的后端就完成了。