016
This commit is contained in:
parent
bcbb401b57
commit
cb572fce52
167
weblog-springboot-016/pom.xml
Normal file
167
weblog-springboot-016/pom.xml
Normal file
@ -0,0 +1,167 @@
|
||||
<?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>
|
||||
<!-- 将 Spring Boot 的版本号切换成 2.6 版本 -->
|
||||
<version>2.6.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-springboot</artifactId>
|
||||
<version>${revision}</version>
|
||||
<name>weblog-springboot</name>
|
||||
<!-- 项目描述 -->
|
||||
<description>前后端分离博客 Weblog By 犬小哈</description>
|
||||
|
||||
<!-- 多模块项目父工程打包模式必须指定为 pom -->
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<!-- 子模块管理 -->
|
||||
<modules>
|
||||
<!-- 入口模块 -->
|
||||
<module>weblog-web</module>
|
||||
<!-- 管理后台 -->
|
||||
<module>weblog-module-admin</module>
|
||||
<!-- 通用模块 -->
|
||||
<module>weblog-module-common</module>
|
||||
<!-- JWT 模块 -->
|
||||
<module>weblog-module-jwt</module>
|
||||
</modules>
|
||||
|
||||
|
||||
<!-- 版本号统一管理 -->
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>0.0.1-SNAPSHOT</revision>
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<!-- Maven 相关 -->
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
<!-- 依赖包版本 -->
|
||||
<lombok.version>1.18.28</lombok.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||
<jackson.version>2.15.2</jackson.version>
|
||||
<knife4j.version>4.3.0</knife4j.version>
|
||||
<mybatis-plus.version>3.5.2</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<jjwt.version>0.11.2</jjwt.version>
|
||||
</properties>
|
||||
|
||||
<!-- 统一依赖管理 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-admin</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-jwt</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 常用工具库 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j(API 文档工具) -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Mybatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
<version>${p6spy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<!-- 统一插件管理 -->
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<!-- 使用阿里云的 Maven 仓库源,提升包下载速度 -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aliyunmaven</id>
|
||||
<name>aliyun</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
33
weblog-springboot-016/weblog-module-admin/.gitignore
vendored
Normal file
33
weblog-springboot-016/weblog-module-admin/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
56
weblog-springboot-016/weblog-module-admin/pom.xml
Normal file
56
weblog-springboot-016/weblog-module-admin/pom.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?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>
|
||||
<!-- 指定父项目为 weblog-springboot -->
|
||||
<parent>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-springboot</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-admin</artifactId>
|
||||
<name>weblog-module-admin</name>
|
||||
<description>weblog-admin (负责管理后台相关功能)</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 免写冗余的 Java 样板式代码 -->
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,53 @@
|
||||
package com.quanxiaoha.weblog.admin.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-16 7:53
|
||||
* @description: Knife4j 配置
|
||||
**/
|
||||
@Configuration
|
||||
@EnableSwagger2WebMvc
|
||||
@Profile("dev") // 只在 dev 环境中开启
|
||||
public class Knife4jAdminConfig {
|
||||
|
||||
@Bean("adminApi")
|
||||
public Docket createApiDoc() {
|
||||
Docket docket = new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(buildApiInfo())
|
||||
// 分组名称
|
||||
.groupName("Admin 后台接口")
|
||||
.select()
|
||||
// 这里指定 Controller 扫描包路径
|
||||
.apis(RequestHandlerSelectors.basePackage("com.quanxiaoha.weblog.admin.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
return docket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 API 信息
|
||||
* @return
|
||||
*/
|
||||
private ApiInfo buildApiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("Weblog 博客 Admin 后台接口文档") // 标题
|
||||
.description("Weblog 是一款由 Spring Boot + Vue 3.2 + Vite 4.3 开发的前后端分离博客。") // 描述
|
||||
.termsOfServiceUrl("https://www.quanxiaoha.com/") // API 服务条款
|
||||
.contact(new Contact("犬小哈", "https://www.quanxiaoha.com", "871361652@qq.com")) // 联系人
|
||||
.version("1.0") // 版本号
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.quanxiaoha.weblog.admin.config;
|
||||
|
||||
import com.quanxiaoha.weblog.jwt.config.JwtAuthenticationSecurityConfig;
|
||||
import com.quanxiaoha.weblog.jwt.filter.TokenAuthenticationFilter;
|
||||
import com.quanxiaoha.weblog.jwt.handler.RestAccessDeniedHandler;
|
||||
import com.quanxiaoha.weblog.jwt.handler.RestAuthenticationEntryPoint;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-23 15:48
|
||||
* @description: Spring Security 配置类
|
||||
**/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
|
||||
@Autowired
|
||||
private RestAuthenticationEntryPoint authEntryPoint;
|
||||
@Autowired
|
||||
private RestAccessDeniedHandler deniedHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable(). // 禁用 csrf
|
||||
formLogin().disable() // 禁用表单登录
|
||||
.apply(jwtAuthenticationSecurityConfig) // 设置用户登录认证相关配置
|
||||
.and()
|
||||
.authorizeHttpRequests()
|
||||
.mvcMatchers("/admin/**").authenticated() // 认证所有以 /admin 为前缀的 URL 资源
|
||||
.anyRequest().permitAll() // 其他都需要放行,无需认证
|
||||
.and()
|
||||
.httpBasic().authenticationEntryPoint(authEntryPoint) // 处理用户未登录访问受保护的资源的情况
|
||||
.and()
|
||||
.exceptionHandling().accessDeniedHandler(deniedHandler) // 处理登录成功后访问受保护的资源,但是权限不够的情况
|
||||
.and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 前后端分离,无需创建会话
|
||||
.and()
|
||||
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 将 Token 校验过滤器添加到用户认证过滤器之前
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token 校验过滤器
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public TokenAuthenticationFilter tokenAuthenticationFilter() {
|
||||
return new TokenAuthenticationFilter();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-16 9:28
|
||||
* @description: TODO
|
||||
**/
|
||||
package com.quanxiaoha.weblog.admin;
|
||||
@ -0,0 +1,13 @@
|
||||
package com.quanxiaoha.weblog.admin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class WeblogModuleAdminApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
33
weblog-springboot-016/weblog-module-common/.gitignore
vendored
Normal file
33
weblog-springboot-016/weblog-module-common/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
73
weblog-springboot-016/weblog-module-common/pom.xml
Normal file
73
weblog-springboot-016/weblog-module-common/pom.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?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>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-springboot</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-common</artifactId>
|
||||
<name>weblog-module-common</name>
|
||||
<description>weblog-module-common (此模块用于存放一些通用的功能)</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!-- 免写冗余的 Java 样板式代码 -->
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- AOP 切面 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Mybatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- mysql 依赖 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,17 @@
|
||||
package com.quanxiaoha.weblog.common.aspect;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Documented
|
||||
public @interface ApiOperationLog {
|
||||
/**
|
||||
* API 功能描述
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,102 @@
|
||||
|
||||
package com.quanxiaoha.weblog.common.aspect;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.quanxiaoha.weblog.common.utils.JsonUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ApiOperationLogAspect {
|
||||
|
||||
/** 以自定义 @ApiOperationLog 注解为切点,凡是添加 @ApiOperationLog 的方法,都会执行环绕中的代码 */
|
||||
@Pointcut("@annotation(com.quanxiaoha.weblog.common.aspect.ApiOperationLog)")
|
||||
public void apiOperationLog() {}
|
||||
|
||||
/**
|
||||
* 环绕
|
||||
* @param joinPoint
|
||||
* @return
|
||||
* @throws Throwable
|
||||
*/
|
||||
@Around("apiOperationLog()")
|
||||
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
try {
|
||||
// 请求开始时间
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// MDC
|
||||
MDC.put("traceId", UUID.randomUUID().toString());
|
||||
|
||||
// 获取被请求的类和方法
|
||||
String className = joinPoint.getTarget().getClass().getSimpleName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
|
||||
// 请求入参
|
||||
Object[] args = joinPoint.getArgs();
|
||||
// 入参转 JSON 字符串
|
||||
String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));
|
||||
|
||||
// 功能描述信息
|
||||
String description = getApiOperationLogDescription(joinPoint);
|
||||
|
||||
// 打印请求相关参数
|
||||
log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ",
|
||||
description, argsJsonStr, className, methodName);
|
||||
|
||||
// 执行切点方法
|
||||
Object result = joinPoint.proceed();
|
||||
|
||||
// 执行耗时
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
// 打印出参等相关信息
|
||||
log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ",
|
||||
description, executionTime, JsonUtil.toJsonString(result));
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解的描述信息
|
||||
* @param joinPoint
|
||||
* @return
|
||||
*/
|
||||
private String getApiOperationLogDescription(ProceedingJoinPoint joinPoint) {
|
||||
// 1. 从 ProceedingJoinPoint 获取 MethodSignature
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
// 2. 使用 MethodSignature 获取当前被注解的 Method
|
||||
Method method = signature.getMethod();
|
||||
|
||||
// 3. 从 Method 中提取 LogExecution 注解
|
||||
ApiOperationLog apiOperationLog = method.getAnnotation(ApiOperationLog.class);
|
||||
|
||||
// 4. 从 LogExecution 注解中获取 description 属性
|
||||
return apiOperationLog.description();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转 JSON 字符串
|
||||
* @return
|
||||
*/
|
||||
private Function<Object, String> toJsonStr() {
|
||||
return arg -> JsonUtil.toJsonString(arg);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package com.quanxiaoha.weblog.common.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-17 16:08
|
||||
* @description: 自定义 Jackson
|
||||
**/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
// 初始化一个 ObjectMapper 对象,用于自定义 Jackson 的行为
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// JavaTimeModule 用于指定序列化和反序列化规则
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
|
||||
// 支持 LocalDateTime、LocalDate、LocalTime
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
// 设置时区
|
||||
objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
|
||||
// 设置凡是为 null 的字段,返参中均不返回,请根据项目组约定是否开启
|
||||
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.quanxiaoha.weblog.common.config;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-22 16:52
|
||||
* @description: Mybatis Plus 配置文件
|
||||
**/
|
||||
@Configuration
|
||||
@MapperScan("com.quanxiaoha.weblog.common.domain.mapper")
|
||||
public class MybatisPlusConfig {
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.quanxiaoha.weblog.common.domain.dos;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-22 17:01
|
||||
* @description: TODO
|
||||
**/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@TableName("t_user")
|
||||
public class UserDO {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
private Boolean isDeleted;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.quanxiaoha.weblog.common.domain.dos;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@TableName("t_user_role")
|
||||
public class UserRoleDO {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String username;
|
||||
private String role;
|
||||
private Date createTime;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.quanxiaoha.weblog.common.domain.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserDO;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-22 17:06
|
||||
* @description: TODO
|
||||
**/
|
||||
public interface UserMapper extends BaseMapper<UserDO> {
|
||||
default UserDO findByUsername(String username) {
|
||||
LambdaQueryWrapper<UserDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(UserDO::getUsername, username);
|
||||
return selectOne(wrapper);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.quanxiaoha.weblog.common.domain.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserRoleDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-22 17:06
|
||||
* @description: TODO
|
||||
**/
|
||||
public interface UserRoleMapper extends BaseMapper<UserRoleDO> {
|
||||
/**
|
||||
* 根据用户名查询
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
default List<UserRoleDO> selectByUsername(String username) {
|
||||
LambdaQueryWrapper<UserRoleDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(UserRoleDO::getUsername, username);
|
||||
|
||||
return selectList(wrapper);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-22 16:57
|
||||
* @description: TODO
|
||||
**/
|
||||
package com.quanxiaoha.weblog.common.domain;
|
||||
@ -0,0 +1,34 @@
|
||||
package com.quanxiaoha.weblog.common.enums;
|
||||
|
||||
import com.quanxiaoha.weblog.common.exception.BaseExceptionInterface;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-15 10:33
|
||||
* @description: 响应异常码
|
||||
**/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||
|
||||
// ----------- 通用异常状态码 -----------
|
||||
SYSTEM_ERROR("10000", "出错啦,后台小哥正在努力修复中..."),
|
||||
PARAM_NOT_VALID("10001", "参数错误"),
|
||||
|
||||
|
||||
// ----------- 业务异常状态码 -----------
|
||||
LOGIN_FAIL("20000", "登录失败"),
|
||||
USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"),
|
||||
UNAUTHORIZED("20002", "无访问权限,请先登录!"),
|
||||
FORBIDDEN("20004", "演示账号仅支持查询操作!"),
|
||||
;
|
||||
|
||||
// 异常码
|
||||
private String errorCode;
|
||||
// 错误信息
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.quanxiaoha.weblog.common.exception;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-15 9:54
|
||||
* @description: 通用异常接口
|
||||
**/
|
||||
public interface BaseExceptionInterface {
|
||||
String getErrorCode();
|
||||
|
||||
String getErrorMessage();
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.quanxiaoha.weblog.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-15 9:52
|
||||
* @description: 业务异常
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
public class BizException extends RuntimeException {
|
||||
// 异常码
|
||||
private String errorCode;
|
||||
// 错误信息
|
||||
private String errorMessage;
|
||||
|
||||
public BizException(BaseExceptionInterface baseExceptionInterface) {
|
||||
this.errorCode = baseExceptionInterface.getErrorCode();
|
||||
this.errorMessage = baseExceptionInterface.getErrorMessage();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package com.quanxiaoha.weblog.common.exception;
|
||||
|
||||
import com.quanxiaoha.weblog.common.enums.ResponseCodeEnum;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.bind.BindResult;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-15 10:14
|
||||
* @description: 全局异常处理
|
||||
**/
|
||||
@ControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 捕获自定义业务异常
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ BizException.class })
|
||||
@ResponseBody
|
||||
public Response<Object> handleBizException(HttpServletRequest request, BizException e) {
|
||||
log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMessage());
|
||||
return Response.fail(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获参数校验异常
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ MethodArgumentNotValidException.class })
|
||||
@ResponseBody
|
||||
public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
|
||||
// 参数错误异常码
|
||||
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||
|
||||
// 获取 BindingResult
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
|
||||
Optional.ofNullable(bindingResult.getFieldErrors()).ifPresent(errors -> {
|
||||
errors.forEach(error ->
|
||||
sb.append(error.getField())
|
||||
.append(" ")
|
||||
.append(error.getDefaultMessage())
|
||||
.append(", 当前值: '")
|
||||
.append(error.getRejectedValue())
|
||||
.append("'; ")
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
// 错误信息
|
||||
String errorMessage = sb.toString();
|
||||
|
||||
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||
|
||||
return Response.fail(errorCode, errorMessage);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ AccessDeniedException.class })
|
||||
public void throwAccessDeniedException(AccessDeniedException e) throws AccessDeniedException {
|
||||
// 捕获到鉴权失败异常,主动抛出,交给 RestAccessDeniedHandler 去处理
|
||||
log.info("============= 捕获到 AccessDeniedException");
|
||||
throw e;
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他类型异常
|
||||
* @param request
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler({ Exception.class })
|
||||
@ResponseBody
|
||||
public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
|
||||
log.error("{} request error, ", request.getRequestURI(), e);
|
||||
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-10 9:20
|
||||
* @description: TODO
|
||||
**/
|
||||
package com.quanxiaoha.weblog.common;
|
||||
@ -0,0 +1,27 @@
|
||||
package com.quanxiaoha.weblog.common.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-14 16:27
|
||||
* @description: JSON 工具类
|
||||
**/
|
||||
@Slf4j
|
||||
public class JsonUtil {
|
||||
|
||||
private static final ObjectMapper INSTANCE = new ObjectMapper();
|
||||
|
||||
public static String toJsonString(Object obj) {
|
||||
try {
|
||||
return INSTANCE.writeValueAsString(obj);
|
||||
} catch (JsonProcessingException e) {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.quanxiaoha.weblog.common.utils;
|
||||
|
||||
import com.quanxiaoha.weblog.common.exception.BaseExceptionInterface;
|
||||
import com.quanxiaoha.weblog.common.exception.BizException;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-11 19:50
|
||||
* @description: 响应参数工具类
|
||||
**/
|
||||
@Data
|
||||
public class Response<T> implements Serializable {
|
||||
|
||||
// 是否成功,默认为 true
|
||||
private boolean success = true;
|
||||
// 响应消息
|
||||
private String message;
|
||||
// 异常码
|
||||
private String errorCode;
|
||||
// 响应数据
|
||||
private T data;
|
||||
|
||||
// =================================== 成功响应 ===================================
|
||||
public static <T> Response<T> success() {
|
||||
Response<T> response = new Response<>();
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> success(T data) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
// =================================== 失败响应 ===================================
|
||||
public static <T> Response<T> fail() {
|
||||
Response<T> response = new Response<>();
|
||||
response.setSuccess(false);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> fail(String errorMessage) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setSuccess(false);
|
||||
response.setMessage(errorMessage);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> fail(String errorCode, String errorMessage) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setSuccess(false);
|
||||
response.setErrorCode(errorCode);
|
||||
response.setMessage(errorMessage);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> fail(BizException bizException) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setSuccess(false);
|
||||
response.setErrorCode(bizException.getErrorCode());
|
||||
response.setMessage(bizException.getErrorMessage());
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> fail(BaseExceptionInterface baseExceptionInterface) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setSuccess(false);
|
||||
response.setErrorCode(baseExceptionInterface.getErrorCode());
|
||||
response.setMessage(baseExceptionInterface.getErrorMessage());
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.quanxiaoha.weblog.common;
|
||||
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserDO;
|
||||
import com.quanxiaoha.weblog.common.domain.mapper.UserMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@SpringBootTest
|
||||
class WeblogModuleCommonApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
33
weblog-springboot-016/weblog-module-jwt/.gitignore
vendored
Normal file
33
weblog-springboot-016/weblog-module-jwt/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
70
weblog-springboot-016/weblog-module-jwt/pom.xml
Normal file
70
weblog-springboot-016/weblog-module-jwt/pom.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?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>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-springboot</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-jwt</artifactId>
|
||||
<name>weblog-module-jwt</name>
|
||||
<description>weblog-module-jwt (JWT 模块,管理用户认证、鉴权)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 免写冗余的 Java 样板式代码 -->
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具包 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 通用模块 -->
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,58 @@
|
||||
package com.quanxiaoha.weblog.jwt.config;
|
||||
|
||||
import com.quanxiaoha.weblog.jwt.filter.JwtAuthenticationFilter;
|
||||
import com.quanxiaoha.weblog.jwt.handler.RestAuthenticationFailureHandler;
|
||||
import com.quanxiaoha.weblog.jwt.handler.RestAuthenticationSuccessHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 16:45
|
||||
* @description: 认证功能相关配置
|
||||
**/
|
||||
@Configuration
|
||||
public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
|
||||
|
||||
@Autowired
|
||||
private RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
|
||||
|
||||
@Autowired
|
||||
private RestAuthenticationFailureHandler restAuthenticationFailureHandler;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
// 自定义的用于 JWT 身份验证的过滤器
|
||||
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
|
||||
filter.setAuthenticationManager(httpSecurity.getSharedObject(AuthenticationManager.class));
|
||||
|
||||
// 设置登录认证对应的处理类(成功处理、失败处理)
|
||||
filter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
|
||||
filter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
|
||||
|
||||
// 直接使用 DaoAuthenticationProvider, 它是 Spring Security 提供的默认的身份验证提供者之一
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
// 设置 userDetailService,用于获取用户的详细信息
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
// 设置加密算法
|
||||
provider.setPasswordEncoder(passwordEncoder);
|
||||
httpSecurity.authenticationProvider(provider);
|
||||
// 将这个过滤器添加到 UsernamePasswordAuthenticationFilter 之前执行
|
||||
httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.quanxiaoha.weblog.jwt.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 9:17
|
||||
* @description: 密码加密
|
||||
**/
|
||||
@Component
|
||||
public class PasswordEncoderConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// BCrypt 是一种安全且适合密码存储的哈希算法,它在进行哈希时会自动加入“盐”,增加密码的安全性。
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
System.out.println(encoder.encode("123456"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.quanxiaoha.weblog.jwt.exception;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 17:11
|
||||
* @description: 用户名或者密码为空异常
|
||||
**/
|
||||
public class UsernameOrPasswordNullException extends AuthenticationException {
|
||||
public UsernameOrPasswordNullException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public UsernameOrPasswordNullException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.quanxiaoha.weblog.jwt.filter;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.quanxiaoha.weblog.jwt.exception.UsernameOrPasswordNullException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 9:36
|
||||
* @description: 用户认证过滤器
|
||||
**/
|
||||
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
|
||||
/**
|
||||
* 指定用户登录的访问地址
|
||||
*/
|
||||
public JwtAuthenticationFilter() {
|
||||
super(new AntPathRequestMatcher("/login", "POST"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
// 解析提交的 JSON 数据
|
||||
JsonNode jsonNode = mapper.readTree(request.getInputStream());
|
||||
JsonNode usernameNode = jsonNode.get("username");
|
||||
JsonNode passwordNode = jsonNode.get("password");
|
||||
|
||||
// 判断用户名、密码是否为空
|
||||
if (Objects.isNull(usernameNode) || Objects.isNull(passwordNode)
|
||||
|| StringUtils.isBlank(usernameNode.textValue()) || StringUtils.isBlank(passwordNode.textValue())) {
|
||||
throw new UsernameOrPasswordNullException("用户名或密码不能为空");
|
||||
}
|
||||
|
||||
String username = usernameNode.textValue();
|
||||
String password = passwordNode.textValue();
|
||||
|
||||
// 将用户名、密码封装到 Token 中
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
|
||||
= new UsernamePasswordAuthenticationToken(username, password);
|
||||
return getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package com.quanxiaoha.weblog.jwt.filter;
|
||||
|
||||
import com.quanxiaoha.weblog.jwt.utils.JwtTokenHelper;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-27 16:58
|
||||
* @description: Token 校验过滤器
|
||||
**/
|
||||
@Slf4j
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Value("${jwt.tokenPrefix}")
|
||||
private String tokenPrefix;
|
||||
|
||||
@Value("${jwt.tokenHeaderKey}")
|
||||
private String tokenHeaderKey;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenHelper jwtTokenHelper;
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
// 从请求头中获取 key 为 Authorization 的值
|
||||
String header = request.getHeader(tokenHeaderKey);
|
||||
|
||||
// 判断 value 值是否以 Bearer 开头
|
||||
if (StringUtils.startsWith(header, tokenPrefix)) {
|
||||
// 截取 Token 令牌
|
||||
String token = StringUtils.substring(header, 7);
|
||||
log.info("Token: {}", token);
|
||||
|
||||
// 判空 Token
|
||||
if (StringUtils.isNotBlank(token)) {
|
||||
try {
|
||||
// 校验 Token 是否可用, 若解析异常,针对不同异常做出不同的响应参数
|
||||
jwtTokenHelper.validateToken(token);
|
||||
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
|
||||
// 抛出异常,统一让 AuthenticationEntryPoint 处理响应参数
|
||||
authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 不可用"));
|
||||
return;
|
||||
} catch (ExpiredJwtException e) {
|
||||
authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 已失效"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 从 Token 中解析出用户名
|
||||
String username = jwtTokenHelper.getUsernameByToken(token);
|
||||
|
||||
if (StringUtils.isNotBlank(username)
|
||||
&& Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
|
||||
// 根据用户名获取用户详情信息
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
// 将用户信息存入 authentication,方便后续校验
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
|
||||
userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
// 将 authentication 存入 ThreadLocal,方便后续获取用户信息
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 继续执行写一个过滤器
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.quanxiaoha.weblog.jwt.handler;
|
||||
|
||||
import com.quanxiaoha.weblog.common.enums.ResponseCodeEnum;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import com.quanxiaoha.weblog.jwt.utils.ResultUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-27 17:32
|
||||
* @description: 登录成功访问收保护的资源,但是权限不够
|
||||
**/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
log.warn("登录成功访问收保护的资源,但是权限不够: ", accessDeniedException);
|
||||
// 预留,后面引入多角色时会用到
|
||||
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.FORBIDDEN));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.quanxiaoha.weblog.jwt.handler;
|
||||
|
||||
import com.quanxiaoha.weblog.common.enums.ResponseCodeEnum;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import com.quanxiaoha.weblog.jwt.utils.ResultUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-27 17:27
|
||||
* @description: 用户未登录访问受保护的资源
|
||||
**/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
log.warn("用户未登录访问受保护的资源: ", authException);
|
||||
if (authException instanceof InsufficientAuthenticationException) {
|
||||
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(ResponseCodeEnum.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(authException.getMessage()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.quanxiaoha.weblog.jwt.handler;
|
||||
|
||||
import com.quanxiaoha.weblog.common.enums.ResponseCodeEnum;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import com.quanxiaoha.weblog.jwt.exception.UsernameOrPasswordNullException;
|
||||
import com.quanxiaoha.weblog.jwt.utils.ResultUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 15:19
|
||||
* @description: 认证失败处理器
|
||||
**/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
log.warn("AuthenticationException: ", exception);
|
||||
if (exception instanceof UsernameOrPasswordNullException) {
|
||||
// 用户名或密码为空
|
||||
ResultUtil.fail(response, Response.fail(exception.getMessage()));
|
||||
} else if (exception instanceof BadCredentialsException) {
|
||||
// 用户名或密码错误
|
||||
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.USERNAME_OR_PWD_ERROR));
|
||||
}
|
||||
|
||||
// 登录失败
|
||||
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.LOGIN_FAIL));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.quanxiaoha.weblog.jwt.handler;
|
||||
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import com.quanxiaoha.weblog.jwt.model.LoginRspVO;
|
||||
import com.quanxiaoha.weblog.jwt.utils.JwtTokenHelper;
|
||||
import com.quanxiaoha.weblog.jwt.utils.ResultUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 15:19
|
||||
* @description: 认证成功处理器
|
||||
**/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RestAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
@Autowired
|
||||
private JwtTokenHelper jwtTokenHelper;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
// 从 authentication 对象中获取用户的 UserDetails 实例,这里是获取用户的用户名
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
|
||||
// 通过用户名生成 Token
|
||||
String username = userDetails.getUsername();
|
||||
String token = jwtTokenHelper.generateToken(username);
|
||||
|
||||
// 返回 Token
|
||||
LoginRspVO loginRspVO = LoginRspVO.builder().token(token).build();
|
||||
|
||||
ResultUtil.ok(response, Response.success(loginRspVO));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.quanxiaoha.weblog.jwt.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 9:43
|
||||
* @description: 用户登录
|
||||
**/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class LoginRspVO {
|
||||
|
||||
/**
|
||||
* Token 值
|
||||
*/
|
||||
private String token;
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.quanxiaoha.weblog.jwt.service;
|
||||
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserDO;
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserRoleDO;
|
||||
import com.quanxiaoha.weblog.common.domain.mapper.UserMapper;
|
||||
import com.quanxiaoha.weblog.common.domain.mapper.UserRoleMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 9:14
|
||||
* @description: TODO
|
||||
**/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UserDetailServiceImpl implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private UserRoleMapper userRoleMapper;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 从数据库中查询
|
||||
UserDO userDO = userMapper.findByUsername(username);
|
||||
|
||||
// 判断用户是否存在
|
||||
if (Objects.isNull(userDO)) {
|
||||
throw new UsernameNotFoundException("该用户不存在");
|
||||
}
|
||||
|
||||
// 用户角色
|
||||
List<UserRoleDO> roleDOS = userRoleMapper.selectByUsername(username);
|
||||
|
||||
String[] roleArr = null;
|
||||
|
||||
// 转数组
|
||||
if (!CollectionUtils.isEmpty(roleDOS)) {
|
||||
List<String> roles = roleDOS.stream().map(p -> p.getRole()).collect(Collectors.toList());
|
||||
roleArr = roles.toArray(new String[roles.size()]);
|
||||
}
|
||||
|
||||
// authorities 用于指定角色
|
||||
return User.withUsername(userDO.getUsername())
|
||||
.password(userDO.getPassword())
|
||||
.authorities(roleArr)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package com.quanxiaoha.weblog.jwt.utils;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.CredentialsExpiredException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.security.Key;
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-24 8:16
|
||||
* @description: JWT Token 工具类
|
||||
**/
|
||||
@Component
|
||||
public class JwtTokenHelper implements InitializingBean {
|
||||
|
||||
/**
|
||||
* 签发人
|
||||
*/
|
||||
@Value("${jwt.issuer}")
|
||||
private String issuer;
|
||||
|
||||
/**
|
||||
* Token 失效时间(分钟)
|
||||
*/
|
||||
@Value("${jwt.tokenExpireTime}")
|
||||
private Long tokenExpireTime;
|
||||
/**
|
||||
* 秘钥
|
||||
*/
|
||||
private Key key;
|
||||
|
||||
/**
|
||||
* JWT 解析
|
||||
*/
|
||||
private JwtParser jwtParser;
|
||||
|
||||
/**
|
||||
* 解码配置文件中配置的 Base 64 编码 key 为秘钥
|
||||
* @param base64Key
|
||||
*/
|
||||
@Value("${jwt.secret}")
|
||||
public void setBase64Key(String base64Key) {
|
||||
key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(base64Key));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 JwtParser
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 考虑到不同服务器之间可能存在时钟偏移,setAllowedClockSkewSeconds 用于设置能够容忍的最大的时钟误差
|
||||
jwtParser = Jwts.parserBuilder().requireIssuer(issuer)
|
||||
.setSigningKey(key).setAllowedClockSkewSeconds(10)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Token
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
public String generateToken(String username) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 设置 Token 失效时间
|
||||
LocalDateTime expireTime = now.plusMinutes(tokenExpireTime);
|
||||
|
||||
return Jwts.builder().setSubject(username)
|
||||
.setIssuer(issuer)
|
||||
.setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
|
||||
.setExpiration(Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()))
|
||||
.signWith(key)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Jws<Claims> parseToken(String token) {
|
||||
try {
|
||||
return jwtParser.parseClaimsJws(token);
|
||||
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
|
||||
throw new BadCredentialsException("Token 不可用", e);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new CredentialsExpiredException("Token 失效", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Token 是否可用
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public void validateToken(String token) {
|
||||
jwtParser.parseClaimsJws(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取用户名
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public String getUsernameByToken(String token) {
|
||||
try {
|
||||
Claims claims = jwtParser.parseClaimsJws(token).getBody();
|
||||
String username = claims.getSubject();
|
||||
return username;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个 Base64 的安全秘钥
|
||||
* @return
|
||||
*/
|
||||
private static String generateBase64Key() {
|
||||
// 生成安全秘钥
|
||||
Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);
|
||||
|
||||
// 将密钥进行 Base64 编码
|
||||
String base64Key = Base64.getEncoder().encodeToString(secretKey.getEncoded());
|
||||
|
||||
return base64Key;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String key = generateBase64Key();
|
||||
System.out.println("key: " + key);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package com.quanxiaoha.weblog.jwt.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-04-18 15:05
|
||||
* @description: 响参工具
|
||||
**/
|
||||
public class ResultUtil {
|
||||
|
||||
/**
|
||||
* 成功响参
|
||||
* @param response
|
||||
* @param result
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void ok(HttpServletResponse response, Response<?> result) throws IOException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType("application/json");
|
||||
PrintWriter writer = response.getWriter();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
writer.write(mapper.writeValueAsString(result));
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响参
|
||||
* @param response
|
||||
* @param result
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void fail(HttpServletResponse response, Response<?> result) throws IOException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType("application/json");
|
||||
PrintWriter writer = response.getWriter();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
writer.write(mapper.writeValueAsString(result));
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响参
|
||||
* @param response
|
||||
* @param status 可指定响应码,如 401 等
|
||||
* @param result
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void fail(HttpServletResponse response, int status, Response<?> result) throws IOException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(status);
|
||||
response.setContentType("application/json");
|
||||
PrintWriter writer = response.getWriter();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
writer.write(mapper.writeValueAsString(result));
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.quanxiaoha.weblog.jwt;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class WeblogModuleJwtApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
33
weblog-springboot-016/weblog-web/.gitignore
vendored
Normal file
33
weblog-springboot-016/weblog-web/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
75
weblog-springboot-016/weblog-web/pom.xml
Normal file
75
weblog-springboot-016/weblog-web/pom.xml
Normal file
@ -0,0 +1,75 @@
|
||||
<?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>
|
||||
<!-- 指定父项目为 weblog-springboot -->
|
||||
<parent>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-springboot</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-web</artifactId>
|
||||
<name>weblog-web</name>
|
||||
<description>weblog-web (入口项目,负责博客前台展示相关功能,打包也放在这个模块负责)</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.quanxiaoha</groupId>
|
||||
<artifactId>weblog-module-admin</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 免写冗余的 Java 样板式代码 -->
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- 参数校验 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.springframework.boot</groupId> -->
|
||||
<!-- <artifactId>spring-boot-starter-json</artifactId> -->
|
||||
<!-- </dependency> -->
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.quanxiaoha.weblog.web;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan({"com.quanxiaoha.weblog.*"}) // 多模块项目中,必需手动指定扫描 com.quanxiaoha.weblog 包下面的所有类
|
||||
public class WeblogWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WeblogWebApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.quanxiaoha.weblog.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-16 7:53
|
||||
* @description: Knife4j 配置
|
||||
**/
|
||||
@Configuration
|
||||
@EnableSwagger2WebMvc
|
||||
@Profile("dev") // 只在 dev 环境中开启
|
||||
public class Knife4jConfig {
|
||||
|
||||
@Bean("webApi")
|
||||
public Docket createApiDoc() {
|
||||
Docket docket = new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(buildApiInfo())
|
||||
// 分组名称
|
||||
.groupName("Web 前台接口")
|
||||
.select()
|
||||
// 这里指定 Controller 扫描包路径
|
||||
.apis(RequestHandlerSelectors.basePackage("com.quanxiaoha.weblog.web.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
return docket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 API 信息
|
||||
* @return
|
||||
*/
|
||||
private ApiInfo buildApiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("Weblog 博客前台接口文档") // 标题
|
||||
.description("Weblog 是一款由 Spring Boot + Vue 3.2 + Vite 4.3 开发的前后端分离博客。") // 描述
|
||||
.termsOfServiceUrl("https://www.quanxiaoha.com/") // API 服务条款
|
||||
.contact(new Contact("犬小哈", "https://www.quanxiaoha.com", "871361652@qq.com")) // 联系人
|
||||
.version("1.0") // 版本号
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.quanxiaoha.weblog.web.controller;
|
||||
|
||||
import com.quanxiaoha.weblog.common.utils.JsonUtil;
|
||||
import com.quanxiaoha.weblog.common.utils.Response;
|
||||
import com.quanxiaoha.weblog.web.model.User;
|
||||
import com.quanxiaoha.weblog.common.aspect.ApiOperationLog;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-10 10:34
|
||||
* @description: TODO
|
||||
**/
|
||||
@RestController
|
||||
@Slf4j
|
||||
@Api(tags = "首页模块")
|
||||
public class TestController {
|
||||
|
||||
@PostMapping("/admin/test")
|
||||
@ApiOperationLog(description = "测试接口")
|
||||
@ApiOperation(value = "测试接口")
|
||||
public Response test(@RequestBody @Validated User user) {
|
||||
// 打印入参
|
||||
log.info(JsonUtil.toJsonString(user));
|
||||
|
||||
// 设置三种日期字段值
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
user.setUpdateDate(LocalDate.now());
|
||||
user.setTime(LocalTime.now());
|
||||
|
||||
return Response.success(user);
|
||||
}
|
||||
|
||||
@PostMapping("/admin/update")
|
||||
@ApiOperationLog(description = "测试更新接口")
|
||||
@ApiOperation(value = "测试更新接口")
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
public Response testUpdate() {
|
||||
log.info("更新成功...");
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.quanxiaoha.weblog.web.model;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: 犬小哈
|
||||
* @url: www.quanxiaoha.com
|
||||
* @date: 2023-08-10 10:35
|
||||
* @description: TODO
|
||||
**/
|
||||
@Data
|
||||
@ApiModel(value = "用户实体类")
|
||||
public class User {
|
||||
// 用户名
|
||||
@NotBlank(message = "用户名不能为空") // 注解确保用户名不为空
|
||||
@ApiModelProperty(value = "用户名")
|
||||
private String username;
|
||||
// 性别
|
||||
@NotNull(message = "性别不能为空") // 注解确保性别不为空
|
||||
@ApiModelProperty(value = "用户性别")
|
||||
private Integer sex;
|
||||
|
||||
// 年龄
|
||||
@NotNull(message = "年龄不能为空")
|
||||
@Min(value = 18, message = "年龄必须大于或等于 18") // 注解确保年龄大于等于 18
|
||||
@Max(value = 100, message = "年龄必须小于或等于 100") // 注解确保年龄小于等于 100
|
||||
@ApiModelProperty(value = "年龄")
|
||||
private Integer age;
|
||||
|
||||
// 邮箱
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
@Email(message = "邮箱格式不正确") // 注解确保邮箱格式正确
|
||||
@ApiModelProperty(value = "邮箱")
|
||||
private String email;
|
||||
|
||||
// 创建时间
|
||||
private LocalDateTime createTime;
|
||||
// 更新日期
|
||||
private LocalDate updateDate;
|
||||
// 时间
|
||||
private LocalTime time;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
spring:
|
||||
datasource:
|
||||
# 指定数据库驱动类
|
||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||
# 数据库连接信息
|
||||
url: jdbc:p6spy:mysql://127.0.0.1:3306/weblog?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
|
||||
username: root
|
||||
password: 123456
|
||||
hikari: # 数据库连接池使用 Hikari
|
||||
minimum-idle: 5 # 连接池中最小空闲连接数
|
||||
maximum-pool-size: 20 # 连接池中允许的最大连接数
|
||||
auto-commit: true # 是否自动提交事务
|
||||
idle-timeout: 30000 # 连接在连接池中闲置的最长时间,超过这个时间会被释放。
|
||||
pool-name: Weblog-HikariCP # 自定义连接池的名字
|
||||
max-lifetime: 1800000 # 连接在连接池中的最大存活时间,超过这个时间会被强制关闭。
|
||||
connection-timeout: 30000 # 连接的超时时间
|
||||
connection-test-query: SELECT 1 # 用于测试连接是否可用的SQL查询
|
||||
security:
|
||||
user:
|
||||
name: admin
|
||||
password: 123456
|
||||
@ -0,0 +1,24 @@
|
||||
spring:
|
||||
datasource:
|
||||
# 指定数据库驱动类
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# 数据库连接信息
|
||||
url: jdbc:mysql://127.0.0.1:3306/weblog?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
|
||||
username: root
|
||||
password: 123456
|
||||
hikari: # 数据库连接池使用 Hikari
|
||||
minimum-idle: 5 # 连接池中最小空闲连接数
|
||||
maximum-pool-size: 20 # 连接池中允许的最大连接数
|
||||
auto-commit: true # 是否自动提交事务
|
||||
idle-timeout: 30000 # 连接在连接池中闲置的最长时间,超过这个时间会被释放。
|
||||
pool-name: Weblog-HikariCP # 自定义连接池的名字
|
||||
max-lifetime: 1800000 # 连接在连接池中的最大存活时间,超过这个时间会被强制关闭。
|
||||
connection-timeout: 30000 # 连接的超时时间
|
||||
connection-test-query: SELECT 1 # 用于测试连接是否可用的SQL查询
|
||||
|
||||
|
||||
#=================================================================
|
||||
# log 日志
|
||||
#=================================================================
|
||||
logging:
|
||||
config: classpath:logback-weblog.xml
|
||||
@ -0,0 +1,18 @@
|
||||
spring:
|
||||
profiles:
|
||||
# 默认激活 dev 环境
|
||||
active: dev
|
||||
|
||||
jwt:
|
||||
# 签发人
|
||||
issuer: quanxiaoha
|
||||
# 秘钥
|
||||
secret: jElxcSUj38+Bnh73T68lNs0DfBSit6U3whQlcGO2XwnI+Bo3g4xsiCIPg8PV/L0fQMis08iupNwhe2PzYLB9Xg==
|
||||
# token 过期时间(单位:分钟) 24*60
|
||||
tokenExpireTime: 1440
|
||||
# token 请求头中的 key 值
|
||||
tokenHeaderKey: Authorization
|
||||
# token 请求头中的 value 值前缀
|
||||
tokenPrefix: Bearer
|
||||
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration >
|
||||
<jmxConfigurator/>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
|
||||
<!-- 应用名称 -->
|
||||
<property scope="context" name="appName" value="weblog" />
|
||||
<!-- 自定义日志输出路径,以及日志名称前缀 -->
|
||||
<property name="LOG_FILE" value="D:\\IDEA_Projects\\weblog\\logs\\${appName}.%d{yyyy-MM-dd}"/>
|
||||
<property name="FILE_LOG_PATTERN" value="[TraceId: %X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||
<!--<property name="CONSOLE_LOG_PATTERN" value="${FILE_LOG_PATTERN}"/>-->
|
||||
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件输出的文件名 -->
|
||||
<FileNamePattern>${LOG_FILE}-%i.log</FileNamePattern>
|
||||
<!-- 日志文件保留天数 -->
|
||||
<MaxHistory>30</MaxHistory>
|
||||
<!-- 日志文件最大的大小 -->
|
||||
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</TimeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!-- 格式化输出:%d 表示日期,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度 %errorMessage:日志消息,%n 是换行符-->
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- dev 环境(仅输出到控制台) -->
|
||||
<springProfile name="dev">
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- prod 环境(仅输出到文件中) -->
|
||||
<springProfile name="prod">
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
|
||||
<root level="INFO">
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
@ -0,0 +1,24 @@
|
||||
#3.2.1????
|
||||
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
#3.2.1?????????
|
||||
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
# ???????
|
||||
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
|
||||
#????????
|
||||
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
|
||||
# ???????? sql
|
||||
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
# ?? p6spy driver ??
|
||||
deregisterdrivers=true
|
||||
# ??JDBC URL??
|
||||
useprefix=true
|
||||
# ???? Log ??,????????error,info,batch,debug,statement,commit,rollback,result,resultset.
|
||||
excludecategories=info,debug,result,commit,resultset
|
||||
# ????
|
||||
dateformat=yyyy-MM-dd HH:mm:ss
|
||||
# ???????
|
||||
#driverlist=org.h2.Driver
|
||||
# ?????SQL??
|
||||
outagedetection=true
|
||||
# ?SQL???? 2 ?
|
||||
outagedetectioninterval=2
|
||||
@ -0,0 +1,49 @@
|
||||
package com.quanxiaoha.weblog.web;
|
||||
|
||||
import com.quanxiaoha.weblog.common.domain.dos.UserDO;
|
||||
import com.quanxiaoha.weblog.common.domain.mapper.UserMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
class WeblogWebApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLog() {
|
||||
log.info("这是一行 Info 级别日志");
|
||||
log.warn("这是一行 Warn 级别日志");
|
||||
log.error("这是一行 Error 级别日志");
|
||||
|
||||
// 占位符
|
||||
String author = "犬小哈";
|
||||
log.info("这是一行带有占位符日志,作者:{}", author);
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertTest() {
|
||||
// 构建数据库实体类
|
||||
UserDO userDO = UserDO.builder()
|
||||
.username("犬小哈")
|
||||
.password("123456")
|
||||
.createTime(LocalDateTime.now())
|
||||
.updateTime(LocalDateTime.now())
|
||||
.isDeleted(false)
|
||||
.build();
|
||||
|
||||
userMapper.insert(userDO);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user