開篇名義,在此感謝李仁密老師的《Spring Boot開發小而美的個人博客》課程,本系列文章是對做該項目過程中的一種記錄,也是個人學習的一些心得體會。希望通過本課程不僅可以學會搭建個性化的個人博客,也能將之前學習的東西整合使用,從而達到融會貫通的目的~
1. 功能概覽
小而美的個人博客所提供的功能大致如下圖所示:
功能整體上分爲兩大部分:
- 管理員
- 登錄
- 博客CRUD
- 類別CRUD
- 標籤CRUD
- 前端展示
- 博客首頁
- 博客分類
- 博客標籤
- 博客歸檔
- 導航欄、底部
2. 技術選型
-
前端:HTML、CSS、JS、Thymleaf、Semantic UI
-
數據庫:MySQL
-
工具:IDEA、JDK8、Maven3
-
插件:
3. 環境搭建
3.1 依賴導入
首先在IDEA中創建Spring Boot項目,並導入項目所需的依賴項,例如Thymleaf、mysql-connector、Jpa、DevTools、Aspects、Lomlok……完整的pom.xml如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>dyliang</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dyliang</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.6.Final</version>
</dependency>
<!--實現MakeDown轉HTML-->
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-heading-anchor</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 配置文件
這裏配置文件採用yaml格式,當然properties格式的配置文件同樣可以。首先編寫全局的配置文件application.yaml
spring:
thymeleaf:
mode: HTML
# 運行環境
profiles:
active: dev
# 國際化
messages:
basename: i18n/messages
爲了適配於不同的應用環境,創建application-dev.yaml和application-pro.yaml分別用於開發環境和生產環境
########################
# application-dev.yaml
########################
spring:
# 配置數據源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
# Jpa相關配置項
jpa:
hibernate:
# 根據實體類自動創建表
ddl-auto: update
# 自動打印sql語句
show-sql: true
# 日誌配置
logging:
# 日誌級別
level:
root: info
com.lrm: debug
# 日誌文件目錄
file:
name: log/blog-dev.log
########################
# application-pro.yaml
########################
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
jpa:
hibernate:
ddl-auto: none
show-sql: true
logging:
level:
root: warn
com.lrm: info
file:
name: log/blog-pro.log
server:
port: 8081
同時也可以添加Spring Boot默認的日誌logback的相關配置,編寫配置文件logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--包含Spring boot對logback日誌的默認配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<!--重寫了Spring Boot框架 org/springframework/boot/logging/logback/file-appender.xml 配置-->
<appender name="TIME_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern>
<!--保留歷史日誌一個月的時間-->
<maxHistory>30</maxHistory>
<!--
Spring Boot默認情況下,日誌文件10M時,會切分日誌文件,這樣設置日誌文件會在100M時切分日誌
-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="TIME_FILE" />
</root>
</configuration>
3.3 異常處理
首先,定義常用的錯誤頁面404、500和error,並編寫全局的異常處理類ControllerExceptionHandler:
@ControllerAdvice
public class ControllerExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(Exception.class)
public ModelAndView ExceptionHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Requst URL : {},Exception : {}", request.getRequestURL(),e);
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
ModelAndView mv = new ModelAndView();
mv.addObject("url",request.getRequestURL());
mv.addObject("exception", e);
mv.setViewName("error/error");
return mv;
}
}
並定義資源找不到異常類NotFoundExcepiton
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
public NotFoundException() {}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
3.4 日誌處理
這裏使用Spring Boot中的AOP進行日誌管理,編寫記錄日誌類LogAspect
@Aspect // 表示該類作爲一個切面
@Component // 將其加入到Ioc容器中
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// @pointcut中指定要掃描的內容對應的AspectJ表達式
@Pointcut("execution(* dyliang.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);
}
@AllArgsConstructor
@ToString
private class RequestLog {
private String url;
private String ip;
private String classMethod;
private Object[] args;
}
}
4. 實體類設計
這裏通過Jpa來使用面向對象的思想根據實體類自動的創建表,所以並不需要在數據庫中顯式的創建表,並設置表之間的映射關係。首先分析一下博客系統所設計的實體類:
- 博客類:Blog
- 類別類:Type
- 標籤類:Tag
- 評論類:Comment
- 用戶類:User
它們之間的關係如下所示:
除了不同實體類之間的關係,由於博客評論這裏設計爲兩級結構,因此父評論和下面的子評論還存在一對多的關係。
5. 命名約定
Service/DAO層命名約定:
- 獲取單個對象的方法用get做前綴。
- 獲取多個對象的方法用list做前綴。
- 獲取統計值的方法用count做前綴。
- 插入的方法用save(推薦)或insert做前綴。
- 刪除的方法用remove(推薦)或delete做前綴。
- 修改的方法用update做前綴。