<!-- Space: qifu -->
<!-- Parent: 后端技术&知识&规范 -->
<!-- Parent: 技术方案 -->
<!-- Parent: 基建 -->
<!-- Parent: 00-基础组件 -->
<!-- Parent: 00-EngineStarterWeb使用指南 -->
<!-- Title: 20250605-engine-starter-web-2.0.1 -->

<!-- Macro: :anchor\((.*)\):
     Template: ac:anchor
     Anchor: ${1} -->
<!-- Macro: \!\[.*\]\((.+)\)\<\!\-\- width=(.*) \-\-\>
     Template: ac:image
     Url: ${1}
     Width: ${2} -->
<!-- Macro: \<\!\-\- :toc: \-\-\>
     Template: ac:toc
     Printable: 'false'
     MinLevel: 2 
     MaxLevel: 4 -->
<!-- Include: 杂项/声明文件.md -->

<!-- :toc: -->

# 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.1-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

<dependency>
    <groupId>com.yuanmeng.engine</groupId>
    <artifactId>engine-starter-web</artifactId>
</dependency>
```

### 添加兼容包（可选）

- 项目中存在 `web-core` 或者 `oauth2-core` 时需要添加兼容包

```xml

<dependency>
    <groupId>com.yuanmeng.engine</groupId>
    <artifactId>engine-starter-compatible-web</artifactId>
</dependency>
```

----

## 使用工具

### 忽略结果封装

```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<String> {

    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> string = (Collection<String>) ids;
        Map<String, String> 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<TestData> autofill() {
        List<TestData> 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<TestDataSecond> autofillSecond() {
        List<TestDataSecond> 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
```