记录生活中的点点滴滴

0%

SpringBoot博客项目-未完结

前些天草草过了一下SpringBoot总的不难但是学习起来很没意思原理那些东西也不太容易搞懂所以先做一个博客项目来巩固一下SpringBoot的学习特此记录一下项目的构建过程

因为一些原因这个项目只是开了个头但是让我对SpringBoot有了更好的学习与理解暂且就这样吧

项目简介

个人博客功能

技术组合

  • 后端Spring Boot + JPA + thymeleaf模板
  • 数据库MySQL
  • 前端UISemantic UI框架

工具与环境

  • IDEA
  • Maven 3
  • JDK 8

页面设计与开发

我们要先快速搭建好前端环境我看了看视频因为我们主要是搞后端的前端这一部分了解即可

前端部分的代码文件如下

这里还需要有一些插件的集成我之前并没有学习过所以这部分我写成了博客记录一下学习过程如下

框架搭建

构建与配置

引入SpringBoot模块

  • web
  • Thymeleaf
  • MySQL
  • DevTools

然后完善配置文件 application.yaml

1
2
3
4
5
spring:
thymeleaf:
mode: HTML
profiles:
active: dev

两套配置文件 application-pro.yaml application-dev.yaml 分别对应生产环境与开发环境

application-pro.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 2000922...

logging:
level:
root: info
cn.gs: debug
file:
name: log/blog-pro.log

application-dev.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 端口设置
server:
port: 8081
spring:
# 数据库连接设置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 2000922...
# 日志设置
logging:
level:
root: info
cn.gs: debug
file:
name: log/blog-dev.log

异常处理

然后先设置404页面和500页面以及index页面先随便写写进行测试

新建 IndexController 文件

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
int i = 9/0;
return "index";
}
}

然后运行起来之后浏览器打开8081因为有异常页面就会定义到500页面如果后面加点字母系统找不到页面就会定向到404页面当然如果把 int i = 9/0; 这一行注释重新运行项目8081端口则会定向到index页面

还要进行异常的处理使用拦截器

先定义一个错误页面error.html

然后完善异常处理器 ControllerExceptionHandler 类的编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice
public class ControllerExceptionHandler {

//处理日志
private Logger logger = LoggerFactory.getLogger(this.getClass());

@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request,Exception e){
logger.error("Request URL : {}, Exception : {}",request.getRequestURL(),e);
ModelAndView mv = new ModelAndView();
mv.addObject("url", request.getRequestURL());
mv.addObject("exception",e);
mv.setViewName("error/error");
return mv;
}
}

然后把 IndexController 类的 int i = 9/0; 这个语句的注释删除重新启动项目访问8081端口

如下可以看到页面定位到错误页面并且控制台会报日志信息

下面我们完成在页面输出相关错误信息以供开发查看

我们在错误页面加入 Thymeleaf 的一些标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>错误</title>
</head>
<body>
<h1>错误</h1>
<div>
<div th:utext="'&lt;!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL : ' + ${url}" th:remove="tag">
</div>
<div th:utext="'Exception message : ' + ${exception.message}"
th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag"><span
th:utext="${st}" th:remove="tag"></span></li>
</ul>
<div th:utext="'--&gt;'" th:remove="tag"></div>
</div>
</body>
</html>

主要就是添加了那个div元素然后重启访问主页就可以看到相关信息

下面完成资源找不到定位到404页面

先在 IndexController 中制造这个情况让它返回异常

1
2
3
4
5
6
7
8
9
@GetMapping("/")
public String index(){
// int i = 9/0;
String blog = null;
if (blog == null){
throw new NotFoundException("博客不存在");
}
return "index";
}

然后我们定义这个 NotFoundException 类让它继承 RuntimeException 类重写方法并加上注解

这个时候我们再访问8081端口发现仍然是错误页面为什么

因为刚刚我们定义的拦截器把这种情况拦截了所以我们需要在拦截器中加入判断说明如果是注解 HttpStatus.NOT_FOUND 这种情况则不要拦截器处理如下

![](8.png)
1
2
3
if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null){
throw e;
}

日志处理

我们要先明白记录日志的内容

  • 请求url
  • 请求ip
  • 调用方法classMethod
  • 参数args
  • 返回内容

处理日志我们要使用横切AOP的思想下面我们先把简单的框架搭一下

先定义一个 LogAspect 类在里面定义切片已经相关之前之后的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
@Component
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Pointcut("execution(* cn.gs.controller.*.*(..))")
public void log(){}

@Before("log()")
public void doBefore(){
logger.info("-------Before--------");
}

@After("log()")
public void doAfter(){
logger.info("--------After---------");
}

@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result){
logger.info("Result : {}",result);
}
}

然后在 IndexController 类中编写如下代码进行测试

1
2
3
4
5
6
7
8
@Controller
public class IndexController {
@GetMapping("/{id}/{name}")
public String index(@PathVariable Integer id, @PathVariable String name){
System.out.println("---------index-------");
return "index";
}
}

启动项目就可以看到如下顺序打印

证明我们的横切代码没有问题

好的下面我们就完善代码定义一个内部类记录相关内容完整的 LogAspect 类的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Aspect
@Component
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

//定义切面
@Pointcut("execution(* cn.gs.controller.*.*(..))")
public void log(){}

@Before("log()")
public void doBefore(JoinPoint joinPoint){
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//请求url
String url = request.getRequestURL().toString();
//请求ip地址
String ip = request.getRemoteAddr();
//请求类方法
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
//请求参数
Object[] args = joinPoint.getArgs();

RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
logger.info("Request ------- {}", requestLog);

}

@After("log()")
public void doAfter(){
// logger.info("--------After---------");
}

@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result){
logger.info("Result : {}",result);
}

private class RequestLog{
private String url;
private String ip;
private String classMethod;
private Object[] args;

@Override
public String toString() {
return "{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}

public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
}
}

好的接下来我们仍然访问 http://localhost:8081/3/zs 查看日志就会显示如下

页面处理

先需要把之前写的静态页面导入到项目中公共资源放在 static/ 目录下相关html页面放到 templates/ 目录下即可

中间我这边也出现了静态资源找不到的问题后续查阅解决了借此写一下原理加深记忆

静态资源访问问题

只要静态资源放在类路径下 called /static (or /public or /resources or /META-INF/resources

访问 当前项目根路径/ + 静态资源名

原理 静态映射/**

请求进来先去找Controller看能不能处理不能处理的所有请求又都交给静态资源处理器静态资源也找不到则响应404页面

改变默认的静态资源路径

1
2
3
4
5
6
spring:
mvc:
static-path-pattern: /res/**

resources:
static-locations: [classpath:/haha/]

静态资源访问前缀默认无前缀

1
2
3
spring:
mvc:
static-path-pattern: /res/**

当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

实体设计

实体类

  • 博客 Blog
  • 博客分类 Type
  • 博客标签 Tag
  • 博客评论 Comment
  • 用户 User

实体关系

评论类自关联关系

Blog类

Type类

Tag类

Comment类

User类

命名约定

Service/DAO层命名约定

  • 获取单个对象的方法用get做前缀
  • 获取多个对象的方法用list做前缀
  • 获取统计值的方法用count做前缀
  • 插入的方法用save(推荐)或insert做前缀
  • 删除的方法用remove(推荐)或delete做前缀
  • 修改的方法用update做前缀

后台管理功能实现

登录

环境搭建

先创建对应的数据表

接下来利用 mybatis 框架进行对应方法的编写其实就是根据邮箱账号和密码进行sql查询返回User对象

先完成dao层创建mapper

1
2
3
4
5
@Mapper
@Repository
public interface UserMapper {
User queryUserByPassword(String email, String password);
}

然后定义mybatis的mapper配置文件UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<?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="cn.gs.dao.UserMapper">
<select id="queryUserByPassword" resultType="cn.gs.pojo.User">
select * from t_user where email = #{email} and password = #{password}
</select>
</mapper>

然后完善 application.yaml 配置文件

1
2
3
mybatis:
type-aliases-package: cn.gs.pojo
mapper-locations: classpath:mybatis/mapper/*.xml

然后再写Service层UserService

1
2
3
public interface UserService {
User checkUser(String username, String password);
}

UserServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public User checkUser(String username, String password) {
User user = userMapper.queryUserByPassword(username,password);
return user;
}
}

下面先进行一下测试

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
class BlogApplicationTests {
@Autowired
private UserMapper userMapper;

@Test
void contextLoads() {
User user = userMapper.queryUserByPassword("917139754@qq.com","123456");
System.out.println(user);
}

}

日期类型为空出了点小问题先不管那么多基本没有问题

登录业务设计

然后写一下业务控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Controller
@RequestMapping("/admin")
public class LoginController {
@Autowired
private UserService userService;

//admin后面什么都不加就跳转到登录页面
@GetMapping
public String loginPage(){
return "admin/login";
}

@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session,
RedirectAttributes attributes){
User user = userService.checkUser(username,password);
if (user != null) {
user.setPassword(null);
session.setAttribute("user",user);
return "admin/index";
} else {
attributes.addFlashAttribute("message","用户名或密码有错误");
return "redirect:/admin";
}
}
@GetMapping("/logout")
public String logout(HttpSession session){
session.removeAttribute("user");
return "redirect:/admin";
}
}

利用session保存信息成功登录后将user信息保存在session中登录不成功重定向到页面并给出提示注销时清空session然后再进行重定向页面

MD5加密

先创建工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MD5Utils {
/**
* MD5加密类
* @param str 要加密的字符串
* @return 加密后的字符串
*/
public static String code(String str){
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte[]byteDigest = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < byteDigest.length; offset++) {
i = byteDigest[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
//32位加密
return buf.toString();
// 16位的加密
//return buf.toString().substring(8, 24);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}

}
public static void main(String[] args) {
System.out.println(code("123456"));
}
}

然后记得修改一下service层的代码

登录拦截器

下面我们开放一下blogs页面在 controller/admin 下创建 BlogController

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/admin")
public class BlogController {

@GetMapping("/blogs")
public String list(){
return "admin/blogs";
}
}

然后访问可以看到对应blogs页面我们需要一个登录拦截器来拦截未登录的用户

实现这一功能我们需要使用spring下的interceptor来完成

1
2
3
4
5
6
7
8
9
10
11
12
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if(request.getSession().getAttribute("user") == null){
response.sendRedirect("/admin");
return false;
}
return true;
}
}

还需要对应的配置类来说明我们是来拦截 /admin 下的请求

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/admin/**") //拦截 /admin 下所有的
.excludePathPatterns("/admin") //放行 /admin
.excludePathPatterns("/admin/login"); // 放行 /admin/login
super.addInterceptors(registry);
}
}

这个项目后续还有很多但是因为它的dao层使用的不是mybatis框架很多注解什么的都看的不是很明白所以先暂且做到这里吧通过这两天的学习让我对SpringBoot有了更深入的学习挺好的