This commit is contained in:
wol 2024-12-22 21:06:51 +08:00
parent 531039cc95
commit 455f4395bf
30 changed files with 1171 additions and 0 deletions

View File

@ -0,0 +1,128 @@
<?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>
</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>
</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.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>
<!-- knife4jAPI 文档工具) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j.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>

View 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/

View File

@ -0,0 +1,45 @@
<?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>
<!-- 免写冗余的 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>
</dependencies>
</project>

View File

@ -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();
}
}

View File

@ -0,0 +1,7 @@
/**
* @author: 犬小哈
* @url: www.quanxiaoha.com
* @date: 2023-08-16 9:28
* @description: TODO
**/
package com.quanxiaoha.weblog.admin;

View File

@ -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() {
}
}

View 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/

View File

@ -0,0 +1,51 @@
<?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>
</dependencies>
</project>

View File

@ -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 "";
}

View File

@ -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);
}
}

View File

@ -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();
// 支持 LocalDateTimeLocalDateLocalTime
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;
}
}

View File

@ -0,0 +1,31 @@
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", "参数错误"),
// ----------- 业务异常状态码 -----------
PRODUCT_NOT_FOUND("20000", "该产品不存在(测试使用)"),
;
// 异常码
private String errorCode;
// 错误信息
private String errorMessage;
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -0,0 +1,85 @@
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.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);
}
/**
* 其他类型异常
* @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);
}
}

View File

@ -0,0 +1,7 @@
/**
* @author: 犬小哈
* @url: www.quanxiaoha.com
* @date: 2023-08-10 9:20
* @description: TODO
**/
package com.quanxiaoha.weblog.common;

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,13 @@
package com.quanxiaoha.weblog.common;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WeblogModuleCommonApplicationTests {
@Test
void contextLoads() {
}
}

View 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/

View 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>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,47 @@
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.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("/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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
#=================================================================
# log 日志
#=================================================================
logging:
config: classpath:logback-weblog.xml

View File

@ -0,0 +1,6 @@
spring:
profiles:
# 默认激活 dev 环境
active: dev

View File

@ -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>

View File

@ -0,0 +1,26 @@
package com.quanxiaoha.weblog.web;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class WeblogWebApplicationTests {
@Test
void contextLoads() {
}
@Test
void testLog() {
log.info("这是一行 Info 级别日志");
log.warn("这是一行 Warn 级别日志");
log.error("这是一行 Error 级别日志");
// 占位符
String author = "犬小哈";
log.info("这是一行带有占位符日志,作者:{}", author);
}
}