前些天草草过了一下SpringBoot总的不难但是学习起来很没意思原理那些东西也不太容易搞懂所以先做一个博客项目来巩固一下SpringBoot的学习特此记录一下项目的构建过程
因为一些原因这个项目只是开了个头但是让我对SpringBoot有了更好的学习与理解暂且就这样吧
项目简介
个人博客功能

技术组合
- 后端Spring Boot + JPA + thymeleaf模板
- 数据库MySQL
- 前端UISemantic UI框架
工具与环境
页面设计与开发
我们要先快速搭建好前端环境我看了看视频因为我们主要是搞后端的前端这一部分了解即可
前端部分的代码文件如下
这里还需要有一些插件的集成我之前并没有学习过所以这部分我写成了博客记录一下学习过程如下
框架搭建
构建与配置
引入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="'<!--'" 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="'-->'" th:remove="tag"></div> </div> </body> </html>
|
主要就是添加了那个div元素然后重启访问主页就可以看到相关信息

下面完成资源找不到定位到404页面
先在 IndexController
中制造这个情况让它返回异常
1 2 3 4 5 6 7 8 9
| @GetMapping("/") public String index(){
String blog = null; if (blog == null){ throw new NotFoundException("博客不存在"); } return "index"; }
|
然后我们定义这个 NotFoundException
类让它继承 RuntimeException
类重写方法并加上注解

这个时候我们再访问8081端口发现仍然是错误页面为什么
因为刚刚我们定义的拦截器把这种情况拦截了所以我们需要在拦截器中加入判断说明如果是注解 HttpStatus.NOT_FOUND
这种情况则不要拦截器处理如下

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(); String url = request.getRequestURL().toString(); 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(){
}
@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;
@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 {
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)); } return buf.toString(); } 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/**") .excludePathPatterns("/admin") .excludePathPatterns("/admin/login"); super.addInterceptors(registry); } }
|
这个项目后续还有很多但是因为它的dao层使用的不是mybatis框架很多注解什么的都看的不是很明白所以先暂且做到这里吧通过这两天的学习让我对SpringBoot有了更深入的学习挺好的