feat:整理博客目录结构

This commit is contained in:
2026-06-05 15:53:29 +08:00
parent a5976c9783
commit a64f20e84f
7 changed files with 539 additions and 9 deletions

View File

@@ -1,877 +0,0 @@
---
title: SpringBoot Common
date: 2025-12-15
---
# 一、框架说明
  本框架基于Spring Boot3框架二次开发增加了依赖包管理和启动项配置等功能。
# 二、项目结构
| 项目模块 | 模块含义 | 主要功能 |
| :-----------: | :-------------------: | :---------------------------------: |
| starters | SpringBoot 启动项 | 主要包括web、jdbc和log启动项配置 |
| autoconfigure | staters具体启动配置类 | 主要包括web和jdbc具体的启动项配置类 |
| dependencies | 依赖项 | 主要包括本框架中的依赖包管理 |
| framework | 通用配置 | 主要包括web和data的一些通用工具方法 |
# 三、项目说明
## 3.1 common模块
  该模块主要声明项目结构包括autoconfigure、dependencies、framework、starter-parent和starters等模块。
```xml
<modules>
<!-- modules表示聚合关系即common有以下模块 -->
<module>dependencies</module>
<module>starters</module>
<module>framework</module>
<module>autoconfigure</module>
<module>starter-parent</module>
</modules>
```
## 3.2 dependencies模块
&emsp;&emsp;该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过`<dependencyManagement>`管理,只声明依赖的版本,并不会实际引入依赖。
&emsp;&emsp;后续有新增依赖时需要先在dependencies模块声明版本然后在相应的starter模块中增加依赖。
```xml
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<spring.boot.version>3.5.0</spring.boot.version>
<springdoc.version>2.7.0</springdoc.version>
<logback-more-appenders.version>1.8.8</logback-more-appenders.version>
<fluency-fluentd.version>2.7.0</fluency-fluentd.version>
<mybatis-spring.version>3.0.4</mybatis-spring.version>
<mybatis.plus.version>3.5.12</mybatis.plus.version>
<sa.token.version>1.43.0</sa.token.version>
<hutool.version>5.8.26</hutool.version>
<commons.io.version>2.19.0</commons.io.version>
<commons.collections.version>4.4</commons.collections.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
<commons.net.version>3.9.0</commons.net.version>
<guava.version>31.1-jre</guava.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<yitter.idgenerator.version>1.0.6</yitter.idgenerator.version>
<lombok.version>1.18.30</lombok.version>
<lombok.mapstruct.version>0.2.0</lombok.mapstruct.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<docker-maven-plugin.version>0.41.0</docker-maven-plugin.version>
<native-maven-plugin.version>0.10.2</native-maven-plugin.version>
</properties>
<!-- dependencyManagement部分只声明依赖的版本并不会实际引入依赖。
需要在具体的模块中显式声明依赖,才能让模块使用这些库 -->
<dependencyManagement>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringDoc OpenAPI + Swagger UI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- 扩展 logback appender -->
<dependency>
<groupId>com.sndyuk</groupId>
<artifactId>logback-more-appenders</artifactId>
<version>${logback-more-appenders.version}</version>
</dependency>
<!-- Fluentd 日志搜集和转发 -->
<dependency>
<groupId>org.komamitsu</groupId>
<artifactId>fluency-fluentd</artifactId>
<version>${fluency-fluentd.version}</version>
</dependency>
<!-- SaToken BOM -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-bom</artifactId>
<version>${sa.token.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- hutool BOM -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-bom</artifactId>
<version>${hutool.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- MyBatis-Plus Maven BOM -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-bom</artifactId>
<version>${mybatis.plus.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- IO工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!-- 集合工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections.version}</version>
</dependency>
<!-- 字符串工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- 网络工具类 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>${commons.net.version}</version>
</dependency>
<!-- 工具类集合、缓存、并发、IO、字符串 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>${guava.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 实体映射工具类 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- 雪花ID生成器 -->
<dependency>
<groupId>com.github.yitter</groupId>
<artifactId>yitter-idgenerator</artifactId>
<version>${yitter.idgenerator.version}</version>
</dependency>
<!-- starter中的依赖 -->
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-logging</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-web</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-jdbc</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-maven-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
```
::: tip
dependencyManagement部分只声明依赖的版本并不会实际引入依赖。
需要在具体的模块中显式声明依赖,才能让模块使用这些库
:::
## 3.3 framework模块
&emsp;&emsp;该模块主要是一些通用的配置。
### 3.3.1 data
&emsp;&emsp;主要提供了2个数据库实体类的基类。
::: code-group
```java [AbstractIdEntity]
@Getter
@Setter
public abstract class AbstractIdEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
}
```
```java [AbstractEntity]
@Getter
@Setter
public abstract class AbstractEntity extends AbstractIdEntity {
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
}
```
:::
### 3.3.2 web
&emsp;&emsp;主要提供了自定义异常处理类。
::: code-group
```java [ErrorResponse]
/**
* 错误响应
*/
public final class ErrorResponse {
/**
* 自定义code
*/
private final String code;
/**
* 自定义消息
*/
private final String message;
public ErrorResponse(String message) {
this.code = "Unspecified";
this.message = message;
}
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
```
```java [CustomException]
/**
* 自定义异常类
*/
public class CustomException extends AbstractException {
public CustomException(String errorMessage) {
super(errorMessage);
}
public CustomException(String code, String errorMessage) {
super(code, errorMessage);
}
public CustomException(String errorMessage, Exception innerException) {
super(errorMessage, innerException);
}
public CustomException(String code, String errorMessage, Exception innerException) {
super(code, errorMessage, innerException);
}
}
```
```java [AbstractException]
/**
* 抽象异常类
*/
public abstract class AbstractException extends RuntimeException {
protected String code;
public AbstractException(String errorMessage) {
super(errorMessage);
}
public AbstractException(String code, String errorMessage) {
super(errorMessage);
this.code = code;
}
public AbstractException(String errorMessage, Exception innerException) {
super(errorMessage, innerException);
}
public AbstractException(String code, String errorMessage, Exception innerException) {
super(errorMessage, innerException);
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
```
:::
### 3.4 starters模块
&emsp;&emsp;该模块为整个项目的核心模块在实际项目中通过引入相应的starter模块并配合autoconfigure模块中的功能即可实现快速自动装配。
&emsp;&emsp;该模块的pom.xml声明的依赖会注入到实际的项目中。
::: danger
在SringBoot3中自动配置的路径改成了**main->resources->META-INF->spring->org.springframework.boot.autoconfigure.AutoConfiguration.imports**路径,然后在该文件中声明需要自动执行的类。
之前的SringBoot2是在**main->resources->META-INF**目录下新建spring.factories文件。
:::
#### 3.4.1 stater-jdbc
&emsp;&emsp;该模块主要提供数据库相关的配置功能。
&emsp;&emsp;目前实现的功能有:
1. 配置MybatisPlus拦截器添加乐观锁和分页插件。
2. 自定义MybatisPlus ID生成器雪花ID
3. 配置MybatisPlus自动填充字段create_time、create_by、update_time和update_by
::: code-group
```java [JdbcAutoConfiguration]
/**
* jdbc自动配置
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SqlSessionFactory.class)
@Import({YitterGenerator.class, MybatisMetaObjectHandler.class})
public class JdbcAutoConfiguration {
/**
* MybatisPlus拦截器
* @return 拦截器
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
```
```java [YitterGenerator]
/**
* 雪花id生成器
*/
public class YitterGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
return YitIdHelper.nextId();
}
}
```
```java [MybatisMetaObjectHandler]
/**
* Mybatis Plus自动填充
*/
public class MybatisMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, Constants.CREATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
if (StpUtil.isLogin()) {
this.strictInsertFill(metaObject, Constants.CREATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
this.strictInsertFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
}
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
if (StpUtil.isLogin()) {
this.strictUpdateFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
}
}
}
```
:::
&emsp;&emsp;`@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
&emsp;&emsp;`@Import`用于导入其他配置类或组件到当前配置类中。
#### 3.4.2 starter-web
&emsp;&emsp;该模块主要提供web相关的配置功能。
&emsp;&emsp;目前实现的功能有:
1. 注册审计拦截器
2. 注册Sa-Token拦截器
3. 注册安全拦截器
4. 配置CORS跨越
5. 全局异常处理器
::: code-group
```java [ServerAutoConfiguration]
@Configuration()
@Import({DefaultExceptionAdvice.class, AuditBodyAdvice.class, CustomProperties.class})
public class ServerAutoConfiguration implements WebMvcConfigurer {
@Resource
private CustomProperties customProperties;
/**
* 注册拦截器 需要实现 WebMvcConfigurer 接口
* @param registry 注册器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册安全拦截器
// registry.addInterceptor(new SecurityInterceptor());
// 注册审计拦截器
registry.addInterceptor(new AuditInterceptor(customProperties.getBasePackage()));
// 注册Sa-Token拦截器 登录校验
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.excludePathPatterns("/error", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**")
.excludePathPatterns("/files/**")
.excludePathPatterns("/doc.html", "/webjars/**");
}
/**
* 配置CORS跨越 需要实现 WebMvcConfigurer 接口
* @return 过滤器
*/
@Bean
@Order(-128)
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许所有来源
config.addAllowedOrigin("*");
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有请求方法
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
```
```java [AuditInterceptor]
public class AuditInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
private final String basePackage;
public AuditInterceptor(String basePackage) {
this.basePackage = basePackage;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果是Http请求
if (handler instanceof HandlerMethod hd) {
request.setAttribute(Constants.AUDIT_TIME_FLAG, System.currentTimeMillis());
// 判断该请求是否需要审计
if (WebUtils.checkIsAuditPackages(hd.getBeanType().getPackage(), basePackage)) {
handleAuditPackageRequest(request);
}
}
// true表示继续处理请求
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 这里我们不需要处理时间计算,所有的计算在 afterCompletion 中完成
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 获取开始时间
Long startTime = (Long) request.getAttribute(Constants.AUDIT_TIME_FLAG);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
logger.info("<AuditSummary> Request URL {} | Time Taken {} ms", request.getRequestURI(), duration);
}
}
/**
* 处理审计包请求
* @param request 请求
*/
private void handleAuditPackageRequest(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getQueryString())) {
logger.info("<AuditSummary> {} {}", request.getMethod(), request.getRequestURI());
} else {
logger.info("<AuditSummary> {} {}?{}", request.getMethod(), request.getRequestURI(), WebUtils.format2UTF8(request.getQueryString()));
}
}
}
```
```java [DefaultExceptionAdvice]
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class DefaultExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionAdvice.class);
/**
* 处理SaToken权限错误
* @param e 权限异常
* @return 错误响应
*/
@ExceptionHandler(SaTokenException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleSaTokenException(SaTokenException e) {
ErrorResponse response = new ErrorResponse(String.valueOf(e.getCode()), e.getMessage());
logger.error("<{}> {}", response.getCode(), e.toString());
return response;
}
/**
* 处理参数校验错误
* @param e 校验异常
* @return 错误响应
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse handleValidationException(MethodArgumentNotValidException e) {
ErrorResponse response = new ErrorResponse(WebUtils.formatValidationException(e.getBindingResult().getFieldErrors()));
logger.error("<{}> {}", response.getCode(), response.getMessage());
return response;
}
/**
* 处理自定义异常错误
* @param e 自定义异常
* @return 错误响应
*/
@ExceptionHandler(AbstractException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleAbstractException(AbstractException e) {
ErrorResponse response;
if (e.getCode() == null || e.getCode().isEmpty()) {
response = new ErrorResponse(e.getMessage());
} else {
response = new ErrorResponse(e.getCode(), e.getMessage());
}
logger.error(String.format("<%s> ", response.getCode()), e);
return response;
}
/**
* 处理默认异常错误
* @param e 默认异常
* @return 错误响应
*/
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleDefaultException(Exception e) {
ErrorResponse response = new ErrorResponse(e.getMessage());
logger.error(String.format("<%s> ", response.getCode()), e);
return response;
}
}
```
```java [AuditBodyAdvice]
@ControllerAdvice
@Import({CustomProperties.class})
public class AuditBodyAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
@Resource
private CustomProperties customProperties;
private final ObjectMapper objectMapper = new ObjectMapper();
private static final Logger logger = LoggerFactory.getLogger(AuditBodyAdvice.class);
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 判断是否为需要审计的包
String auditPackages = customProperties.getBasePackage();
return WebUtils.checkIsAuditPackages(methodParameter.getDeclaringClass().getPackage(), auditPackages)
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 入参结束
try {
String jsonBody = objectMapper.writeValueAsString(body);
logger.info("<AuditRequest> {}", jsonBody);
} catch (JsonProcessingException e) {
logger.info("<AuditResponse> {}", body);
}
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 判断是否为需要审计的包
String auditPackages = customProperties.getBasePackage();
return WebUtils.checkIsAuditPackages(returnType.getDeclaringClass().getPackage(), auditPackages)
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 出参结束
if (body != null && !(body instanceof ErrorResponse)) {
try {
String jsonBody = objectMapper.writeValueAsString(body);
logger.info("<AuditResponse> {}", jsonBody);
} catch (JsonProcessingException e) {
logger.info("<AuditResponse> {}", body);
}
}
return body;
}
}
```
```java [CustomProperties]
/**
* 用于将配置文件(如 application.properties 或 application.yml中的属性值绑定到 Java 对象
*/
@ConfigurationProperties(prefix = "web-starter")
public class CustomProperties {
// yml中web-starter下的base-package字段值
private String basePackage = "";
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
}
```
:::
&emsp;&emsp;`@ConfigurationProperties`用于将配置文件(如 application.properties 或 application.yml中的属性值绑定到 Java 对象
::: tip
SpringBoot MVC总体执行顺序
1. 进入Tomcat容器
2. 进入Filter过滤器
3. 进入Servlet容器
4. 进入Interceptor拦截器
5. 进入Controller控制器
6. 进入AOP
:::
::: tip
请求和响应体拦截器
RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后
1. preHandle():请求处理前
2. beforeBodyRead请求体反序列化前
3. @RequestBodyController方法参数绑定
4. Controller请求处理
5. beforeBodyWrite():响应体序列化之前
6. postHandle():请求处理后
7. 视图渲染
8. afterCompletion():请求结束
:::
::: tip
审计拦截器 可以实现请求日志打印等功能
HandlerInterceptor 拦截器执行顺序:
1. preHandle():请求处理前 按注册顺序依次执行。
2. Controller请求处理 请求到达Controller并被处理。
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
:::
# 四、项目发布
## 4.1 发布到Git中
1. 在Gitee/Github中创建工程需要在工程中创建一个文件夹例如repo后续发布的文件要放在该文件夹下。
2. 在maven的setting.xml中添加server和repository信息
```xml
<servers>
<server>
<id>gitee</id>
<username>Cxx0822</username>
<password>token</password>
</server>
</servers>
<profiles>
<profile>
<id>gitee</id>
<repositories>
<repository>
<id>central</id>
<url>https://maven.aliyun.com/repository/central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>gitee</id>
<url>https://gitee.com/Cxx0822/springboot2-common/raw/master/repo</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://maven.aliyun.com/repository/central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>gitee</activeProfile>
</activeProfiles>
```
&emsp;&emsp;注这里要去掉mirror的阿里云镜像。
3. 在工程的根目录的pom.xml中添加发布配置
```xml
<distributionManagement>
<repository>
<id>gitee</id>
<name>springboot2-common</name>
<url>file:D:/temp/maven</url>
</repository>
</distributionManagement>
```
&emsp;&emsp;注gitee不支持通过deploy发布jar包可以先发布到本地再将文件复制到项目文件夹下通过git push推送。
4. 将本地产生的发布文件上传至git仓库中。
5. 其他项目引用:
```xml
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-web</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-jdbc</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-logging</artifactId>
<version>1.0.0</version>
</dependency>
```
# 五、项目使用
1. 将父工程改为starter-parent模块
```xml
<parent>
<groupId>com.cxx</groupId>
<artifactId>starter-parent</artifactId>
<version>2.0.0</version>
<relativePath/>
</parent>
```
2. 根据需要引入starter-web、starter-jdbc和starter-logging模块
```xml
<dependencies>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-web</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-jdbc</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-logging</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
```
3. 打包模块时需要添加spring-boot-maven-plugin
```xml
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
```

View File

@@ -1,419 +0,0 @@
---
title: Vue3-Common
date: 2025-12-15 23:25
---
# 一、依赖管理
&emsp;&emsp;本项目会传递安装的依赖有:
| 包名称 | 版本 | 含义和用途说明 |
|-------|------|---------------|
| `@fortawesome/fontawesome-free` | ^6.7.2 | FontAwesome 图标库的免费版本 |
| `@popperjs/core` | ^2.11.8 | 工具提示和弹出框定位引擎 |
| `axios` | ^1.4.0 | 基于 Promise 的 HTTP 客户端 |
| `compressorjs` | ^1.2.1 | 纯 JavaScript 图片压缩库 |
| `crypto-js` | ^4.2.0 | JavaScript 加密算法库 |
| `dayjs` | ^1.11.13 | 轻量级的日期处理库 |
| `echarts` | ^5.5.1 | 百度开源的数据可视化图表库 |
| `element-plus` | ^2.6.0 | 基于 Vue 3 的桌面端 UI 组件库 |
| `js-cookie` | ^3.0.5 | JavaScript Cookie 操作库 |
| `lunar-calendar` | ^0.1.4 | 农历日历转换库 |
| `lunar-javascript` | ^1.6.13 | 农历日期处理的 JavaScript 库 |
| `path-browserify` | ^1.0.1 | Node.js path 模块的浏览器版本兼容实现 |
| `qs` | ^6.13.0 | URL 查询字符串解析和序列化库 |
| `v-calendar` | ^3.1.2 | Vue.js 的日历和日期选择器组件 |
::: tip
如果使用pnpm安装会传递peerDependencies部分。
:::
::: warning
建议将Vite、Typescript、@types等构建工具依赖放在devDependencies中
:::
# 二、vite.config.ts配置
## 2.1 生成Typescript类型文件
&emsp;&emsp;安装vite-plugin-dts插件
```cmd
pnpm add vite-plugin-dts -D
```
```typescript
import dts from 'vite-plugin-dts'
// 打包输出文件夹
const outDirPath = 'dist'
// 需要打包的类型文件
const TARGET_TYPE_FOLDERS = ['src/components', 'src/utils', 'src/types', 'src/vue3-common.ts']
export default defineConfig({
plugins: [
// 生成类型文件
dts({
// 需要处理的文件
include: TARGET_TYPE_FOLDERS,
// 使用特定的 tsconfig 配置
tsconfigPath: path.resolve(__dirname, 'tsconfig.app.json'),
// 类型文件输出目录
outDir: path.resolve(__dirname, outDirPath)
})
],
})
```
&emsp;&emsp;打包后会在dist目录生成.d.ts文件提供完整的Typescript类型支持。
## 2.2 build打包配置
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
import path from 'path'
import fs from 'fs'
import { ICommonObj } from './src/types'
// 打包输出文件夹
const outDirPath = 'dist'
// 需要单个打包的文件夹(组件和工具类)
const TARGET_LIB_FOLDERS = ['src/components', 'src/utils']
// 需要排除的第三方依赖
const EXTERNAL = ['vue', 'element-plus', 'echarts', 'axios', 'moment', 'crypto-js', 'spark-md5', 'path-browserify']
/**
* 递归读取文件夹下的所有文件
* @param folderPath 文件夹
*/
const getFilesFromFolder = (folderPath: string) => {
const files: string[] = []
// 读取文件夹中的内容
fs.readdirSync(folderPath).forEach((item: string) => {
const fullPath = path.join(folderPath, item)
// 读取文件状态 如果是文件夹,递归读取
if (fs.statSync(fullPath).isDirectory()) {
files.push(...getFilesFromFolder(fullPath))
} else {
// 否则添加至文件列表
files.push(fullPath)
}
})
return files
}
// 动态生成入口文件
const inputEntries = TARGET_LIB_FOLDERS.reduce((entries: ICommonObj, folder) => {
// 获取文件夹内的所有文件
const files = getFilesFromFolder(path.resolve(__dirname, folder))
files.forEach((filePath) => {
// 判断文件是否是 .vue 或 .ts 文件
if (filePath.endsWith('.vue') || filePath.endsWith('.ts')) {
// 获取相对路径并保持目录结构
const relativePath = path.relative('src', filePath)
const entryName = path.join('lib', relativePath.replace(/\.(vue|ts)$/, ''))
entries[entryName] = filePath // 为每个文件创建一个入口
}
})
return entries
}, {})
// https://vitejs.dev/config/
export default defineConfig({
build: {
// lib文件配置
lib: {
// 入口文件
entry: path.resolve(__dirname, 'src/vue3-common.ts'),
formats: ['es'],
name: 'vue3-common',
// 文件名
fileName: (format) => `vue3-common.${format}.js`
},
// 输出文件路径
outDir: outDirPath,
// 是否将css文件分割
cssCodeSplit: true,
// Rollup打包配置
rollupOptions: {
// 需要排除的依赖 通常为第三方库
external: EXTERNAL,
// 输入配置
input: {
// 入口文件
'vue3-common': path.resolve(__dirname, 'src/vue3-common.ts'),
// 其余需要单独打包的文件
...inputEntries
},
// 输出配置
output: {
dir: path.resolve(__dirname, outDirPath),
// 入口文件
entryFileNames: '[name].js',
// chunk文件
chunkFileNames: 'lib/[name].js',
// 资源文件
assetFileNames: 'styles/[name].[ext]'
}
},
// 是否压缩代码
minify: false
}
})
```
&emsp;&emsp;打包后会在dist目录生成源码文件。
# 三、package.json配置
## 3.1 导出路径
```json
{
"exports": {
".": {
"import": "./dist/vue3-common.js",
"require": "./dist/vue3-common.js",
"types": "./dist/vue3-common.d.ts"
},
"./components/SvgIcon.vue": {
"import": "./dist/lib/components/SvgIcon.js",
"types": "./dist/components/SvgIcon.vue.d.ts"
},
"./styles/SvgIcon.css": "./dist/styles/SvgIcon.css",
"./components/LoginForm.vue": {
"import": "./dist/lib/components/LoginForm.js",
"types": "./dist/components/LoginForm.vue.d.ts"
},
"./styles/LoginForm.css": "./dist/styles/LoginForm.css",
"./components/MenuItem.vue": {
"import": "./dist/lib/components/MenuItem.js",
"types": "./dist/components/MenuItem.vue.d.ts"
},
"./components/MenuList.vue": {
"import": "./dist/lib/components/MenuList.js",
"types": "./dist/components/MenuList.vue.d.ts"
},
"./components/Hamburger.vue": {
"import": "./dist/lib/components/Hamburger.js",
"types": "./dist/components/Hamburger.vue.d.ts"
},
"./styles/Hamburger.css": "./dist/styles/Hamburger.css",
"./components/MultiInput.vue": {
"import": "./dist/lib/components/MultiInput.js",
"types": "./dist/components/MultiInput.vue.d.ts"
},
"./styles/MultiInput.css": "./dist/styles/MultiInput.css",
"./types": {
"types": "./dist/types/index.d.ts"
},
"./utils/axiosUtil": {
"import": "./dist/lib/utils/axiosUtil.js",
"types": "./dist/types/utils/axiosUtil.d.ts"
},
"./utils/cryptoUtil": {
"import": "./dist/lib/utils/cryptoUtil.js",
"types": "./dist/types/utils/cryptoUtil.d.ts"
},
"./utils/dataUtil": {
"import": "./dist/lib/utils/dataUtil.js",
"types": "./dist/types/utils/dataUtil.d.ts"
},
}
}
```
&emsp;&emsp;在项目中引入common包后通过配置tsconfig.json即可实现按需引入功能`import { fun1 } from 'vue3-common/utils/index'`
```json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"vue3-common/*": ["node_modules/vue3-common/dist/*"]
}
}
}
```
# 四、发布框架
1. 发布到本地
执行`npm pack`命令,会在项目文件夹下生成`.tgz`文件,其他项目通过文件路径形式引入:
```json
"vue3-common": "file:../vue3-common/vue3-common-1.0.0.tgz"
```
2. 发布到git
将dist文件夹发布到git仓库其他项目通过git形式引入
```json
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common#master"
```
# 五、组件使用说明
## 5.1 菜单组件
1. 安装Vue Router
```cmd
pnpm install vue-router
```
2. 在src/views文件夹中新建vue文件例如
```cmd
views
viewA
index.vue
viewB
index.vue
```
3. 在src/views文件夹下新建meta.ts配置文件
```typescript
import type { IRouteMetaConfig } from 'vue3-common/types'
export const metaList: IRouteMetaConfig = {
'/viewA': {
title: 'viewA',
icon: 'viewA',
order: 1,
redirect: '/viewA/index'
},
'/viewA/index': {
title: 'viewA-Index',
icon: ''
},
'/viewB': {
title: 'viewB',
icon: 'viewB',
order: 2,
redirect: '/viewB/index'
},
'/viewB/index': {
title: 'viewB-index',
icon: ''
}
}
```
&emsp;&emsp;order对应的显示顺序关系icon对应的为src/icons/svg中的svg图标文件参考下文图标组件
4. 在src目录新建router文件夹新建menu.ts文件
```typescript
import { getRoutersByModules, sortRoutesByOrder } from 'vue3-common/utils/routerUtil'
import Layout from '@/layout/index.vue'
import { metaList } from '@/views/meta'
const modules = {
...import.meta.glob('@/views/viewA/**/*.vue'),
...import.meta.glob('@/views/viewB/**/*.vue'),
}
const menuRoutes = getRoutersByModules(modules, Layout, metaList)
export default sortRoutesByOrder(menuRoutes)
```
&emsp;&emsp;导入刚才的views文件夹并生成路由菜单。
5. 如果还有其他的常量路由可以在src/router中新建constant.ts文件
```typescript
import type { RouteRecordRaw } from 'vue-router'
const constantRoutes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/login'
},
// 主页
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
}
]
export default constantRoutes
```
&emsp;&emsp;该部分即Vue Router中的路由定义。
6. 在src/router中新建index.ts
```typescript
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { setupRouteGuard } from 'vue3-common/utils/permissionUtil'
// 使用 import.meta.glob 自动导入所有 src/router 目录下的 .ts 文件
const routeModules = import.meta.glob('./*.ts', { eager: true })
// 将所有模块的默认导出(即路由配置)合并成一个路由数组
const routes: RouteRecordRaw[] = Object.values(routeModules)
.map((module: any) => module.default) // 获取每个模块的默认导出
.flat() // 扁平化数组,确保所有路由项都在一个数组中
const router = createRouter({
history: createWebHashHistory(),
scrollBehavior: () => ({ top: 0 }),
routes
})
// 设置路由守卫
setupRouteGuard(router)
export default router
```
&emsp;&emsp;遍历src/router文件夹下所有的路由文件并添加路由守卫。
7. 在main.ts中配置路由
```ts
// 引入路由
import router from './router'
// 创建Vue3实例
const app = createApp(App)
// 使用路由
app.use(router)
```
## 5.2 图标组件
1. 安装vite-plugin-svg-icons依赖
```cmd
pnpm install vite-plugin-svg-icons -D
```
2. 配置vite.config.ts
```typescript
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定 SVG图标 保存的文件夹路径
iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
// 指定 使用svg图标的格式
symbolId: 'icon-[dir]-[name]'
})
],
})
```
3. 在main.ts中注册
```typescript
// 注册svg-icon
import 'virtual:svg-icons-register'
```
4. 在src/icons/svg目录中添加svg图标
5. 在组件中使用
```vue
<template>
<svg-icon name="user"/>
</template>
<script setup>
import { SvgIcon } from 'vue3-common'
</script>
```

View File

@@ -0,0 +1,93 @@
---
title: Vue3 defineModel
date: 2026-06-05
---
# 一、概念
&emsp;&emsp;defineModel是对 props + emit的语法糖返回一个可写的 ref。Vu3.4开始支持。
&emsp;&emsp;之前的做法:
```ts
// 子组件
const props = defineProps({
modelValue: Number
})
const emit = defineEmits(['update:modelValue'])
function increment() {
emit('update:modelValue', props.modelValue + 1)
}
```
&emsp;&emsp;现在做法:
```ts
const model = defineModel()
```
# 二、简单示例
&emsp;&emsp;父组件:
```vue
<script setup>
import Stepper from './Stepper.vue'
import { ref } from 'vue'
const quantity = ref(1)
</script>
<template>
<div>
<p>购买数量{{ quantity }}</p>
<Stepper v-model="quantity" />
</div>
</template>
```
&emsp;&emsp;子组件:
```vue
<script setup>
const model = defineModel({
type: Number,
default: 1
})
</script>
<template>
<button @click="quantity--"></button>
<span>{{ quantity }}</span>
<button @click="quantity++">+</button>
</template>
```
&emsp;&emsp;`defineProps`定义类似,可以指定类型和默认值。
# 三、多个v-model
&emsp;&emsp;父组件:
```vue
<UserForm
v-model:name="form.name"
v-model:age="form.age"
/>
```
&emsp;&emsp;子组件:
```vue
<script setup lang="ts">
const name = defineModel<string>('name', {
default: ''
})
const age = defineModel<number>('age', {
default: 0
})
</script>
<template>
<input v-model="name" placeholder="姓名" />
<input v-model="age" type="number" placeholder="年龄" />
</template>
```
::: tip
defineProps: 只读,不能改
defineModel: 可写
:::

View File

@@ -0,0 +1,79 @@
---
title: Vue3自定义指令
date: 2026-06-05
---
# 一、概念
&emsp;&emsp;自定义指令Directive 是 Vue 提供的一种机制,用于直接操作 DOM封装可复用的底层 DOM 行为。它是对模板能力的补充当现有模板语法v-if / v-bind / v-on不够用时就可以用指令。
# 二、简单示例
&emsp;&emsp;注册:
```ts
app.directive('demo', {
mounted(el, binding) {
console.log(el, binding)
}
})
```
&emsp;&emsp;使用:
```vue
<div v-demo="'hello'"></div>
```
&emsp;&emsp;使用该指令,就会触发打印效果。
# 三、生命周期
| 钩子 | 说明 |
| ----- | ----- |
| created | 绑定前 |
| beforeMount | 元素挂载前 |
| mounted | 元素已挂载 |
| beforeUpdate | 更新前 |
| updated | 更新后 |
| beforeUnmount | 卸载前 |
| unmounted | 卸载后 |
# 四、参数
&emsp;&emsp;mounted(el, binding, vnode, prevVnode)
| 参数 | 说明 |
| ----- | ----- |
| el | 当前 DOM 元素 |
| binding.value | 指令绑定的值 |
| binding.arg | 参数 |
| binding.modifiers | 修饰符 |
| vnode | 虚拟节点 |
# 五、封装
&emsp;&emsp;`src`目录新建`directives/**.ts`,定义指令:
```ts
import { useAuthStore } from '@/stores/auth'
export default {
mounted(el, binding) {
const authStore = useAuthStore()
const permission = binding.value
const hasPermission = authStore.currentUser?.permission?.includes(permission)
if (!hasPermission) {
el.parentNode?.removeChild(el)
}
}
}
```
&emsp;&emsp;该指令根据用户权限控制DOM是否显示。
&emsp;&emsp;然后在`main.ts`中注册指令:
```ts
import permissionDirective from '@/directives/permission'
app.directive('permission', permissionDirective)
```
&emsp;&emsp;最后在需要的元素中加上`v-permission=""`即可。
::: tip
自定义指令只做DOM相关的事情比如隐藏DOM禁止点击、修改样式等等。
:::