diff --git a/文档/基础组件/20250509-engine-starter-feign-1.1.0.md b/文档/基础组件/feign/20250509-engine-starter-feign-1.1.0.md
similarity index 100%
rename from 文档/基础组件/20250509-engine-starter-feign-1.1.0.md
rename to 文档/基础组件/feign/20250509-engine-starter-feign-1.1.0.md
diff --git a/文档/基础组件/feign/20250527-engine-starter-feign-2.0.0.md b/文档/基础组件/feign/20250527-engine-starter-feign-2.0.0.md
new file mode 100644
index 0000000..9202680
--- /dev/null
+++ b/文档/基础组件/feign/20250527-engine-starter-feign-2.0.0.md
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# engine-starter-feign 使用教程
+
+> 基于 Feign 的通用封装
+> 具体使用可参考 [engine-sample > engine-sample-starter-web](../engine-sample/engine-sample-starter-web)
+
+## 功能特性
+
+- [X] 请求头添加内部请求标识 `X-QiFu-From-In: true`
+- [X] 请求头透传
+- [X] 请求结果解析映射(解封装 `Result`)
+
+-------
+
+## 快速使用
+
+- **注意:** `qifu-saas-parent >= 2.0.0-SNAPSHOT`
+- **注意:** 启动类 `@ComponentScan` 需要变更为如下形式
+- ```java
+ @ComponentScan(value = "com.yuanmeng.*",
+ excludeFilters = {
+ @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
+ @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
+ })
+ @SpringBootApplication
+ public class SampleStarterWebApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleStarterWebApplication.class, args);
+ }
+
+ }
+ ```
+
+### 添加依赖
+
+```xml
+
+
+ com.yuanmeng.engine
+ engine-starter-feign
+
+```
+
+### 添加兼容包(可选)
+
+- 项目中存在 `web-core` 时需要添加兼容包
+
+```xml
+
+
+ com.yuanmeng.engine
+ engine-starter-compatible-feign
+
+```
+
+------
+
+## 完整默认配置文件
+
+```yaml
+yuanmeng:
+ feign:
+ #- 开启feign自动配置
+ enable: true
+ #- 开启请求头内部标识
+ enable-contract: true
+ #- 开启旧包兼容
+ enable-ignore: true
+ #- 开启结果解析
+ enable-decoder: true
+ #- 开启请求头传递
+ enable-interceptor: true
+ #- 链接超时时间
+ connect-timeout: 10
+ connect-time-unit: seconds
+ #- 读取超时时间
+ read-timeout: 60
+ read-time-unit: seconds
+```
\ No newline at end of file
diff --git a/文档/基础组件/20250526-engine-starter-mybatis-plus-2.0.0.md b/文档/基础组件/mybatis/20250526-engine-starter-mybatis-plus-2.0.0.md
similarity index 95%
rename from 文档/基础组件/20250526-engine-starter-mybatis-plus-2.0.0.md
rename to 文档/基础组件/mybatis/20250526-engine-starter-mybatis-plus-2.0.0.md
index 25cb4d1..40046c0 100644
--- a/文档/基础组件/20250526-engine-starter-mybatis-plus-2.0.0.md
+++ b/文档/基础组件/mybatis/20250526-engine-starter-mybatis-plus-2.0.0.md
@@ -79,6 +79,18 @@
```
+### 添加兼容包(可选)
+
+- 项目中存在 `data-mybatis-plus` 时需要添加兼容包
+
+```xml
+
+
+ com.yuanmeng.engine
+ engine-starter-compatible-mybatis-plus
+
+```
+
----
## 使用工具
diff --git a/文档/基础组件/20250411-engine-starter-redis-1.0.14.md b/文档/基础组件/redis/20250411-engine-starter-redis-1.0.14.md
similarity index 97%
rename from 文档/基础组件/20250411-engine-starter-redis-1.0.14.md
rename to 文档/基础组件/redis/20250411-engine-starter-redis-1.0.14.md
index 419cb20..888303a 100644
--- a/文档/基础组件/20250411-engine-starter-redis-1.0.14.md
+++ b/文档/基础组件/redis/20250411-engine-starter-redis-1.0.14.md
@@ -32,8 +32,7 @@
- [X] `@YmIdempotent`: 基于 Redis 的幂等工具
- [X] `@YmRateLimit`: 基于 Redisson 的限流工具
- [X] `@Lock4j`: 基于 Lock4j 的分布式锁工具
- - [X] 旧包中的 `@DistributedMultiLock` 慢慢迁移到新注解
- - [X] TODO 旧包中的 `@DistributedMultiLock` 需要实现 `@Lock4jMulti`
+- [X] `@Lock4jMulti`: 多锁模式
- [X] `@Cacheable`: 基于 Redis 的 SpringCache 集成
- [X] 扩展 key,支持 key 中自定义时间 `abc#1000`
- [X] `YmRedisClient`: Redis 操作工具类,基于 `StringRedisTemplate`
@@ -261,5 +260,5 @@ yuanmeng:
prefix: rate_limit
#- Lock4j Redisson 分布式锁配置
lock4j-redisson:
- enable: false
+ enable: true
```
diff --git a/文档/基础组件/20250411-engine-starter-seata-1.0.14.md b/文档/基础组件/seata/20250411-engine-starter-seata-1.0.14.md
similarity index 100%
rename from 文档/基础组件/20250411-engine-starter-seata-1.0.14.md
rename to 文档/基础组件/seata/20250411-engine-starter-seata-1.0.14.md
diff --git a/文档/基础组件/20250527-EngineStarter引入指南-2.0.0.md b/文档/基础组件/starter/20250527-EngineStarter引入指南-2.0.0.md
similarity index 100%
rename from 文档/基础组件/20250527-EngineStarter引入指南-2.0.0.md
rename to 文档/基础组件/starter/20250527-EngineStarter引入指南-2.0.0.md
diff --git a/文档/基础组件/20250509-engine-starter-web-1.1.0.md b/文档/基础组件/web/20250509-engine-starter-web-1.1.0.md
similarity index 99%
rename from 文档/基础组件/20250509-engine-starter-web-1.1.0.md
rename to 文档/基础组件/web/20250509-engine-starter-web-1.1.0.md
index 6a3ba51..ddc6d0e 100644
--- a/文档/基础组件/20250509-engine-starter-web-1.1.0.md
+++ b/文档/基础组件/web/20250509-engine-starter-web-1.1.0.md
@@ -361,5 +361,5 @@ yuanmeng:
external-url-list:
- "/external/test"
i18n:
- enable: true
+ enable: false
```
\ No newline at end of file
diff --git a/文档/基础组件/web/20250527-engine-starter-web-2.0.0.md b/文档/基础组件/web/20250527-engine-starter-web-2.0.0.md
new file mode 100644
index 0000000..d8ca4cc
--- /dev/null
+++ b/文档/基础组件/web/20250527-engine-starter-web-2.0.0.md
@@ -0,0 +1,388 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# engine-starter-web 使用教程
+
+> 基于 web 的各种工具封装
+> 具体使用可参考 [engine-sample > engine-sample-starter-web](../engine-sample/engine-sample-starter-web)
+
+## 功能特性
+
+- [X] `@YmAutoFill` 和 `@YmAutoFillResp`: 结果自动填充
+- [X] `@YmIgnoreResultWrapper`: 忽略 `Result` 结果包装
+- [X] `YmResponseAdvice`: 全局结果封装
+- [X] `YmGlobalExceptionHandlerAdvice`: 全局异常拦截处理
+- [X] `YmAutofillResponseAdvice`: 全局返回结果填充处理
+- [X] `YmI18nResponseAdvice`: I18n全局处理
+- [X] `@YmIgnoreI18n`: 忽略I18n
+- [X] `YmHealthCheckFilter`: 全局健康检查接口
+- [X] `YmRequestBodyWrapperFilter`: request 和 response 可重复读封装
+- [X] `YmRequestPrintFilter`: 请求日志打印
+- [X] `YmThreadLocalFilter`: 登录用户信息解析存储(后续会迁移到网关,需要兼容)
+- [X] `YmUserLoginFilter`: 用户登录校验(后续会迁移到网关,默认关闭)
+- [X] `YmThreadLocalUtils`: 获取当前线程中的上下文信息
+
+-------
+
+## 快速使用
+
+- **注意:** `qifu-saas-parent >= 2.0.0-SNAPSHOT`
+- **注意:** 启动类 `@ComponentScan` 需要变更为如下形式
+- ```java
+ @ComponentScan(value = "com.yuanmeng.*",
+ excludeFilters = {
+ @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
+ @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
+ })
+ @SpringBootApplication
+ public class SampleStarterWebApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleStarterWebApplication.class, args);
+ }
+
+ }
+ ```
+
+### 添加依赖
+
+```xml
+
+
+ com.yuanmeng.engine
+ engine-starter-web
+
+```
+
+### 添加兼容包(可选)
+
+- 项目中存在 `web-core` 或者 `oauth2-core` 时需要添加兼容包
+
+```xml
+
+
+ com.yuanmeng.engine
+ engine-starter-compatible-web
+
+```
+
+----
+
+## 使用工具
+
+### 忽略结果封装
+
+```java
+public class Usage {
+
+ /**
+ * 测试忽略包装
+ *
+ * @return 结果
+ */
+ @YmIgnoreResultWrapper
+ @GetMapping("/ignore-result-wrapper")
+ public String ignoreResultWrapper() {
+ return "ignore-result-wrapper success";
+ }
+
+}
+```
+
+### 全局异常拦截
+
+```java
+public class Usage {
+
+ @GetMapping("/exception")
+ public String exception() {
+ // 打印error日志,触发告警
+ throw new RuntimeException("exception");
+ }
+
+ @GetMapping("/ym-exception")
+ public String ymException() {
+ // 打印error日志,触发告警
+ throw new YmException("ym exception");
+ }
+
+ @GetMapping("/ym-biz-exception")
+ public String ymBizException() {
+ // 打印 warning 日志
+ throw new YmBizException(YmResultCode.BASE_ERROR);
+ }
+
+ @GetMapping("/ym-biz-data-exception")
+ public String ymBizDataException() {
+ // 打印 info 日志
+ throw new YmBizDataException(YmResultCode.BASE_ERROR,
+ new TestData(TestStatusEnum.SUCCESS.getCode(), "1"));
+ }
+
+ @YmAutofillResp
+ @GetMapping("/ym-biz-data-exception-fill")
+ public String ymBizDataExceptionFill() {
+ // 触发结果数据自动填充
+ throw new YmBizDataException(YmResultCode.BASE_ERROR,
+ new TestData(TestStatusEnum.SUCCESS.getCode(), "1"));
+ }
+
+}
+```
+
+### 国际化支持
+
+- 开启国际化支撑
+
+```yaml
+yuanmeng:
+ web:
+ i18n:
+ enable: true
+```
+
+- 添加国际化文件
+ - 默认 `resources > i18n > messages_zh_CN.properties`
+
+```properties
+Base\ Error=服务端错误
+```
+
+- 编写代码
+
+```java
+
+@GetMapping("/i18n")
+public String i18n() {
+ return "test success";
+}
+
+@YmIgnoreI18n
+@GetMapping("/i18n-ignore")
+public String i18nIgnore() {
+ return "i18n ignore";
+}
+
+@GetMapping("/i18n-error")
+public String i18nError() {
+ // 触发国际化配置
+ throw new YmBizException(YmResultCode.BASE_ERROR);
+}
+```
+
+- **自定义国际化处理类(可选)**
+
+```java
+
+@RequiredArgsConstructor
+public class CustomI18nMessageHandler implements YmI18nMessageHandler {
+
+ private final MessageSource messageSource;
+
+ @Override
+ public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
+ return messageSource.getMessage(code, args, defaultMessage, locale);
+ }
+
+ @Override
+ public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
+ return messageSource.getMessage(code, args, locale);
+ }
+
+ @Override
+ public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
+ return messageSource.getMessage(resolvable, locale);
+ }
+
+}
+
+@Configuration
+public class YmI18nConfiguration {
+
+ public static final String I18N_MESSAGE_SOURCE = "ymI18nMessageSource";
+ public static final String I18N_MESSAGE_HANDLER = "ymI18nMessageHandler";
+
+ @Bean(I18N_MESSAGE_SOURCE)
+ @ConditionalOnMissingBean(name = I18N_MESSAGE_SOURCE)
+ public MessageSource messageSource() {
+ log.info("[WEB_STARTER_INIT]: CUSTOM I18N_MESSAGE_SOURCE init");
+ ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
+ messageSource.setBasename("i18n/test");
+ messageSource.setDefaultEncoding(StandardCharsets.UTF_8.toString());
+ return messageSource;
+ }
+
+ @Bean(I18N_MESSAGE_HANDLER)
+ @ConditionalOnMissingBean(name = I18N_MESSAGE_HANDLER)
+ public YmI18nMessageHandler ymI18nMessageHandler(@Qualifier(I18N_MESSAGE_SOURCE) MessageSource messageSource) {
+ log.info("[WEB_STARTER_INIT]: CUSTOM YmI18nMessageHandler init");
+ return new CustomI18nMessageHandler(messageSource);
+ }
+
+}
+```
+
+### 结果自动填充
+
+```java
+
+@Getter
+@AllArgsConstructor
+public enum TestStatusEnum implements YmBaseEnum {
+
+ SUCCESS("SUCCESS", "成功"),
+ FAIL("FAIL", "失败"),
+ ;
+
+ private final String code;
+ private final String desc;
+
+}
+
+@Component
+public class TestAutoFillHandler implements YmAutofillHandler {
+
+ @Override
+ public Map, ?> handle(Collection> ids, YmAutofillInfo autofill) {
+ Collection string = (Collection) ids;
+ Map result = new HashMap<>();
+ string.forEach(k -> result.put(k, k + "value"));
+ return result;
+ }
+
+}
+
+@Data
+@NoArgsConstructor
+public class TestData {
+
+ @YmAutofill(enumCls = TestStatusEnum.class)
+ private String statusName;
+ private String status;
+
+ private String code;
+ @YmAutofill(from = "code", type = YmAutofillType.BEAN, handler = TestAutoFillHandler.class)
+ private String msg;
+
+ public TestData(String status, String code) {
+ this.status = status;
+ this.code = code;
+ }
+
+}
+
+@Data
+@NoArgsConstructor
+public class TestDataSecond {
+
+ private String statusName;
+ private String status;
+
+ private String code;
+ private String msg;
+
+ public TestDataSecond(String status, String code) {
+ this.status = status;
+ this.code = code;
+ }
+
+}
+
+
+public class Usage {
+
+ @YmAutofillResp
+ @GetMapping("/autofill")
+ public List autofill() {
+ List list = new ArrayList<>();
+ list.add(new TestData(TestStatusEnum.SUCCESS.getCode(), "1"));
+ list.add(new TestData(TestStatusEnum.FAIL.getCode(), "2"));
+ return list;
+ }
+
+ @YmAutofillResp({
+ @YmAutofill(from = "status", target = "statusName", enumCls = TestStatusEnum.class),
+ @YmAutofill(from = "code", target = "msg", type = YmAutofillType.BEAN, handler = TestAutoFillHandler.class)
+ })
+ @GetMapping("/autofill-second")
+ public List autofillSecond() {
+ List list = new ArrayList<>();
+ list.add(new TestDataSecond(TestStatusEnum.SUCCESS.getCode(), "1"));
+ list.add(new TestDataSecond(TestStatusEnum.FAIL.getCode(), "2"));
+ return list;
+ }
+
+}
+```
+
+------
+
+## 完整默认配置文件
+
+```yaml
+yuanmeng:
+ web:
+ #- 开启starter
+ enable: true
+ #- 开启兼容排除
+ enable-ignore: true
+ #- 开启返回结果填充
+ enable-audit-handler: true
+ #- 开启返回结果自动封装 YmResult
+ enable-result-wrapper: true
+ #- 开启全局异常拦截
+ enable-exception-handler: true
+ filter:
+ #- 开启健康检查接口
+ enable-health-check: true
+ #- 开启上下文信息解析
+ enable-thread-local: true
+ #- 开启登录拦截器(token校验)
+ enable-user-login: false
+ #- 需要跳过的路径
+ skip-path:
+ - "/test"
+ #- 可重复读请求封装
+ request-body:
+ enable: true
+ filter-uris:
+ - "/test1"
+ #- 请求日志打印封装
+ track:
+ enable: true
+ filter-uris:
+ - "/test2"
+ sensitive-keys:
+ - "password"
+ print-type: 2
+ #- 打印请求体
+ print-request-body: true
+ #- 打印返回体
+ print-response-body: true
+ external-url-list:
+ - "/external/test"
+ #- i18n全局拦截
+ i18n:
+ enable: false
+```
\ No newline at end of file