feat:整理博客目录结构
This commit is contained in:
@@ -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模块
|
||||
  该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过`<dependencyManagement>`管理,只声明依赖的版本,并不会实际引入依赖。
|
||||
  后续有新增依赖时,需要先在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模块
|
||||
  该模块主要是一些通用的配置。
|
||||
|
||||
### 3.3.1 data
|
||||
  主要提供了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
|
||||
  主要提供了自定义异常处理类。
|
||||
::: 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模块
|
||||
  该模块为整个项目的核心模块,在实际项目中,通过引入相应的starter模块,并配合autoconfigure模块中的功能,即可实现快速自动装配。
|
||||
  该模块的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
|
||||
  该模块主要提供数据库相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时,这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
|
||||
  `@Import`用于导入其他配置类或组件到当前配置类中。
|
||||
|
||||
#### 3.4.2 starter-web
|
||||
  该模块主要提供web相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@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. @RequestBody:Controller方法参数绑定
|
||||
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>
|
||||
```
|
||||
|
||||
  注:这里要去掉mirror的阿里云镜像。
|
||||
|
||||
3. 在工程的根目录的pom.xml中添加发布配置:
|
||||
```xml
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitee</id>
|
||||
<name>springboot2-common</name>
|
||||
<url>file:D:/temp/maven</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
```
|
||||
|
||||
  注: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>
|
||||
```
|
||||
@@ -1,419 +0,0 @@
|
||||
---
|
||||
title: Vue3-Common
|
||||
date: 2025-12-15 23:25
|
||||
---
|
||||
|
||||
# 一、依赖管理
|
||||
  本项目会传递安装的依赖有:
|
||||
|
||||
| 包名称 | 版本 | 含义和用途说明 |
|
||||
|-------|------|---------------|
|
||||
| `@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类型文件
|
||||
  安装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)
|
||||
})
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
  打包后会在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
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
  打包后会在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"
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  在项目中引入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: ''
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  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)
|
||||
```
|
||||
|
||||
  导入刚才的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
|
||||
```
|
||||
|
||||
  该部分即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
|
||||
```
|
||||
|
||||
  遍历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>
|
||||
```
|
||||
93
docs/Web/Vue/Vue3-DefineModel.md
Normal file
93
docs/Web/Vue/Vue3-DefineModel.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Vue3 defineModel
|
||||
date: 2026-06-05
|
||||
---
|
||||
|
||||
# 一、概念
|
||||
  defineModel是对 props + emit的语法糖,返回一个可写的 ref。Vu3.4开始支持。
|
||||
  之前的做法:
|
||||
```ts
|
||||
// 子组件
|
||||
const props = defineProps({
|
||||
modelValue: Number
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
function increment() {
|
||||
emit('update:modelValue', props.modelValue + 1)
|
||||
}
|
||||
```
|
||||
|
||||
  现在做法:
|
||||
```ts
|
||||
const model = defineModel()
|
||||
```
|
||||
|
||||
# 二、简单示例
|
||||
  父组件:
|
||||
```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>
|
||||
```
|
||||
|
||||
  子组件:
|
||||
```vue
|
||||
<script setup>
|
||||
const model = defineModel({
|
||||
type: Number,
|
||||
default: 1
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="quantity--">−</button>
|
||||
<span>{{ quantity }}</span>
|
||||
<button @click="quantity++">+</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
  和`defineProps`定义类似,可以指定类型和默认值。
|
||||
|
||||
# 三、多个v-model
|
||||
  父组件:
|
||||
```vue
|
||||
<UserForm
|
||||
v-model:name="form.name"
|
||||
v-model:age="form.age"
|
||||
/>
|
||||
```
|
||||
|
||||
  子组件:
|
||||
```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: 可写
|
||||
:::
|
||||
79
docs/Web/Vue/Vue3-Directive.md
Normal file
79
docs/Web/Vue/Vue3-Directive.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Vue3自定义指令
|
||||
date: 2026-06-05
|
||||
---
|
||||
|
||||
# 一、概念
|
||||
  自定义指令(Directive) 是 Vue 提供的一种机制,用于直接操作 DOM,封装可复用的底层 DOM 行为。它是对模板能力的补充,当现有模板语法(v-if / v-bind / v-on)不够用时,就可以用指令。
|
||||
|
||||
# 二、简单示例
|
||||
  注册:
|
||||
```ts
|
||||
app.directive('demo', {
|
||||
mounted(el, binding) {
|
||||
console.log(el, binding)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
  使用:
|
||||
```vue
|
||||
<div v-demo="'hello'"></div>
|
||||
```
|
||||
|
||||
  使用该指令,就会触发打印效果。
|
||||
|
||||
# 三、生命周期
|
||||
| 钩子 | 说明 |
|
||||
| ----- | ----- |
|
||||
| created | 绑定前 |
|
||||
| beforeMount | 元素挂载前 |
|
||||
| mounted | 元素已挂载 |
|
||||
| beforeUpdate | 更新前 |
|
||||
| updated | 更新后 |
|
||||
| beforeUnmount | 卸载前 |
|
||||
| unmounted | 卸载后 |
|
||||
|
||||
# 四、参数
|
||||
  mounted(el, binding, vnode, prevVnode):
|
||||
| 参数 | 说明 |
|
||||
| ----- | ----- |
|
||||
| el | 当前 DOM 元素 |
|
||||
| binding.value | 指令绑定的值 |
|
||||
| binding.arg | 参数 |
|
||||
| binding.modifiers | 修饰符 |
|
||||
| vnode | 虚拟节点 |
|
||||
|
||||
# 五、封装
|
||||
  在`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)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
  该指令根据用户权限控制DOM是否显示。
|
||||
|
||||
  然后在`main.ts`中注册指令:
|
||||
```ts
|
||||
import permissionDirective from '@/directives/permission'
|
||||
|
||||
app.directive('permission', permissionDirective)
|
||||
```
|
||||
|
||||
  最后在需要的元素中加上`v-permission=""`即可。
|
||||
|
||||
::: tip
|
||||
自定义指令只做DOM相关的事情,比如隐藏DOM,禁止点击、修改样式等等。
|
||||
:::
|
||||
Reference in New Issue
Block a user