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