[代码重构](dev): 大重构,分离核心跟springboot引入,增加demo模块

This commit is contained in:
song_jx 2023-05-25 21:02:27 +08:00
parent 110b8afe0d
commit 7bf1fb63be
54 changed files with 840 additions and 307 deletions

1
.gitignore vendored
View File

@ -111,3 +111,4 @@ buildNumber.properties
/.idea/
/.fastRequest/
.flattened-pom.xml

9
CHANGELOG.md Normal file
View File

@ -0,0 +1,9 @@
# ChangeLog
All notable changes to this project will be documented in this fileChangeLog information is generated by the [CommitMessage plugin](https://plugins.jetbrains.com/plugin/12256-commit-message-create)
## [v](https://git/compare/v...master)
## [0.1](https://git/compare/0.1...master)

214
README.md
View File

@ -12,13 +12,15 @@
* [x] 自定义翻译
* [x] 翻译结果脱敏
* [x] json字符串翻译为json对象
* [x] 嵌套翻译
## 项目结构
```
EasyTranslate
└── src
├── main
dict-trans
├── dict-trans-core
│ └── src
│ └── main
│ └── java
│ └── com
│ └── aizuda
@ -27,10 +29,6 @@ EasyTranslate
│ │ ├── Dictionary.java 字典注解,标识在字典数据类上(自动生成查询 SQL
│ │ ├── Translate.java 翻译字段注解,标识在需要翻译的字段上
│ │ └── Translator.java 翻译方法注解,对方法返回值进行翻译
│ ├── aspect
│ │ └── TranslateAspect.java 翻译切面
│ ├── config
│ │ └── TranslatorConfig.java 默认翻译方法注入配置
│ ├── constants 常量配置
│ │ └── DesensitizedTypeConstants.java
│ ├── desensitized 脱敏相关
@ -42,61 +40,133 @@ EasyTranslate
│ │ ├── EnumPool.java
│ │ ├── FormatType.java
│ │ └── IEnum.java
│ ├── handler 主要操作类
│ │ └── TranslatorHandle.java
│ ├── json json相关
│ │ ├── IJsonConvert.java
│ │ └── JSONConvert.java
│ ├── service
│ │ ├── impl
│ │ │ ├── DefaultDictTranslateServiceImpl.java 默认数据字典翻译实现(实现字典翻译接口。仿照该方法,实现自己的业务)
│ │ │ ├── DictCacheTranslator.java 数据字典翻译实现(调用 字典翻译接口实现)
│ │ │ ├── DataBaseTranslator.java 数据库翻译服务
│ │ │ ├── DesensitizedTranslator.java 脱敏实现(没啥操作,就返回原值)
│ │ │ ├── EnumTranslator.java 枚举翻译实现
│ │ │ └── JsonConvertTranslator.java json翻译实现
│ │ ├── Translatable.java 翻译接口(字典、枚举、....接实现该接口)
│ │ └── DictTranslateService.java 字典翻译接口
│ ├── TranslatorBootApplication.java
│ │ ├── DictTranslateService.java 字典翻译接口
│ │ └── Translatable.java 翻译接口(字典、枚举、....接实现该接口)
│ └── util 一些工具类
│ ├── LambdaUtil.java
│ └── NameUtil.java
└── test demo演示
├── dict-trans-demo demo演示
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── aizuda
│ │ │ └── trans
│ │ │ ├── controller
│ │ │ │ └── TestController.java
│ │ │ ├── demo
│ │ │ │ ├── DemoService.java
│ │ │ │ └── impl
│ │ │ │ ├── CustomerTranslateServiceImpl.java
│ │ │ │ ├── DemoServiceImpl.java
│ │ │ │ └── ResultUnWrapper.java 业务统一返回 解包器实现
│ │ │ ├── dict
│ │ │ │ └── CustomerDictImpl.java
│ │ │ ├── entity
│ │ │ │ ├── Device.java
│ │ │ │ ├── Dict.java
│ │ │ │ ├── People.java
│ │ │ │ ├── People2.java
│ │ │ │ ├── People3.java
│ │ │ │ ├── Result.java
│ │ │ │ └── UserDB.java
│ │ │ ├── enums
│ │ │ │ └── DeviceStatus.java
│ │ │ └── TranslatorBootApplication.java
│ │ └── resources
│ │ └── application.yml
│ └── test
│ └── java
│ └── com
│ └── aizuda
│ └── trans
│ └── TranslatorTest.java
├── dict-trans-spring-boot-starter
│ └── src
│ └── main
│ └── java
│ └── com
│ └── aizuda
│ └── trans
│ ├── aspect 翻译切面
│ │ └── TranslateAspect.java
│ ├── config
│ │ └── TranslatorConfig.java 默认翻译方法注入配置
│ ├── handler 主要操作类
│ │ └── TranslatorHandle.java
│ └── service
│ ├── impl
│ │ ├── DataBaseTranslator.java 数据库翻译服务
│ │ ├── DefaultDictTranslateServiceImpl.java 默认数据字典翻译实现(实现字典翻译接口。仿照该方法,实现自己的业务)
│ │ ├── DesensitizedTranslator.java 脱敏实现(没啥操作,就返回原值)
│ │ ├── DictCacheTranslator.java 数据字典翻译实现(调用 字典翻译接口实现)
│ │ ├── EnumTranslator.java 枚举翻译实现
│ │ ├── JsonConvertTranslator.java json翻译实现
│ │ └── wrapper
│ │ └── IPageUnWrapper.java mybatis-plus 解包实现(就是取 records 而已)
│ └── UnWrapper.java 解包接口
└── doc
├── imgs
│ └── demo.png
└── t_test.sql demo 数据库脚本
```
## 快速开始
### 引入jar包
> 不要问为什么强依赖 `Hutool``MyBatis-Plus`
>
> 就是爱!
>
> 不用这两个的,下面可以不用看了!!!
```xml
<dependencies>
<!-- 可自行编译 或 从中央仓库引入 -->
<!-- https://central.sonatype.com/artifact/com.aizuda/dict-trans -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans</artifactId>
<version>0.2</version>
<version>0.3</version>
</dependency>
<!-- hutool工具类 -->
<!-- hutool工具类(必须) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
<version>5.8.18</version>
</dependency>
<!-- 数据库 -->
<!-- mybatis-plus 工具(必须) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
<optional>true</optional>
<version>3.5.3.1</version>
</dependency>
<!-- ====================== 以下的非必须 ====================== -->
<!-- spring-boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.12</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<optional>true</optional>
<version>5.1.49</version>
</dependency>
</dependencies>
```
### 配置
@ -331,13 +401,14 @@ private Object jsonObj;
> 方法返回类型支持 `Map``Entity``List``IPage`
说明:对方法返回值进行翻译,**只能用在方法上**
说明:对方法返回值进行翻译,**可用在方法、字段上**
参数:无
示例:
```java
// =========================== 示例1 用在方法上 ===========================
@Component
public class DemoServiceImpl implements DemoService {
@ -365,12 +436,49 @@ public class DemoServiceImpl implements DemoService {
return CollUtil.newArrayList(man, woman);
}
}
// =========================== 示例2 用在字段上 ===========================
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class People {
@Translate(dictClass = Dict.class, groupValue = "sex", translateField = "sexName")
private String sex;
private String sexName;
/** 手机号脱敏 */
@Translate(dictClass = Desensitized.class, translateField = "phone", desensitizedModel = DesensitizedTypeConstants.MOBILE_PHONE)
private String phone;
/** 自定义翻译 */
@Translate(dictionary = @Dictionary(translator = CustomerTranslateServiceImpl.class), translateField = "name")
private String id;
private String name;
/** 用在字段上,可嵌套翻译 */
@Translator
private Device device;
}
```
## 演示
> 完整示例参考 `dict-trans-demo` 模块
```java
/**
* 转换测试
*
* @author nn200433
* @date 2020年05月18日 0018 15:05:05
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class TranslatorTest {
@ -381,19 +489,31 @@ public class TranslatorTest {
@Test
public void demo1() {
List<People> peopleList = demoService.dictDemo();
Console.log("---> 翻译结果:{}", peopleList);
Console.log("---> 字典 & 脱敏 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo2() {
List<Device> deviceList = demoService.enumDemo();
Console.log("---> 翻译结果:{}", deviceList);
Console.log("---> 枚举 翻译结果:{}", JSONUtil.toJsonStr(deviceList));
}
@Test
public void demo3() {
List<People2> peopleList = demoService.dbDemo();
Console.log("---> 翻译结果:{}", peopleList);
Console.log("---> 数据库 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo4() {
List<People3> peopleList = demoService.jsonDemo();
Console.log("---> json 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo5() {
Result result = demoService.responseNestedMockTest();
Console.log("---> 响应嵌套数据:{}", JSONUtil.toJsonStr(result));
}
}
@ -401,6 +521,37 @@ public class TranslatorTest {
![运行示例](./doc/imgs/demo.png)
```
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.12)
2023-05-25 20:47:53.135 INFO 34112 --- [ main] com.aizuda.trans.TranslatorTest : Starting TranslatorTest using Java 1.8.0_311 on LAPTOP-N9LDSE74 with PID 34112 (started by nn200 in D:\idea_hengfeng\dict-trans\dict-trans-demo)
2023-05-25 20:47:53.139 INFO 34112 --- [ main] com.aizuda.trans.TranslatorTest : No active profile set, falling back to 1 default profile: "default"
2023-05-25 20:47:55.086 WARN 34112 --- [ main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.aizuda.trans]' package. Please check your configuration.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.3.1
2023-05-25 20:48:02.307 INFO 34112 --- [ main] com.aizuda.trans.TranslatorTest : Started TranslatorTest in 10.317 seconds (JVM running for 12.941)
---> 字典 & 脱敏 翻译结果:[{"sex":"1","sexName":"男","phone":"186****5678","id":"1","name":"结果1"},{"sex":"2","sexName":"女","phone":"186****5678","id":"2","name":"结果2"}]
---> 枚举 翻译结果:[{"status":"1","statusName":"未使用"},{"status":"2","statusName":"试运行"}]
2023-05-25 20:48:03.226 INFO 34112 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2023-05-25 20:48:03.913 INFO 34112 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
---> 数据库 翻译结果:[{"id":"1","name":"张三"},{"id":"2","name":"李四"}]
---> json 翻译结果:[{"id":"1","name":"张三","json":"{\"abc\":\"def\", \"eg\":3}","jsonObj":{"abc":"def","eg":3}},{"id":"2","name":"李四","json":"[{\"a\":\"b\",\"c\":6},{\"d\":\"f\",\"e\":{\"a\":\"6\"}}]","jsonObj":[{"a":"b","c":6},{"d":"f","e":{"a":"6"}}]}]
---> 响应嵌套数据:{"status":200,"data":[{"sex":"1","sexName":"男","phone":"186****5678","id":"1","name":"结果1","device":{"status":"1","statusName":"未使用"}},{"sex":"2","sexName":"女","phone":"186****5678","id":"2","name":"结果2","device":{"status":"2","statusName":"试运行"}}]}
2023-05-25 20:48:04.168 INFO 34112 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2023-05-25 20:48:04.194 INFO 34112 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
Disconnected from the target VM, address: '127.0.0.1:1229', transport: 'socket'
Process finished with exit code 0
```
## 附注
该项目得益于 [Transformer v1 版本](https://github.com/luo-zhan/Transformer) 增加了 脱敏,完善了 数据库翻译 等功能。Transformer可能是最简单但最强大的字段转换插件一个注解搞定任意转换让开发变得更加丝滑基本上就是在此项目上增加功能。
@ -411,3 +562,4 @@ public class TranslatorTest {
* [Hutool](https://hutool.cn) Hutool是一个Java工具包让Java语言也可以“甜甜的”。
* [MyBatis-Plus](https://baomidou.com/) MyBatis-Plus (opens new window)(简称 MP是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
* [Transformer](https://github.com/luo-zhan/Transformer) Transformer 是一款功能全面的数据转换工具,只需要几个简单的注解配置,即可实现各种姿势的字段转换,抛弃连表查询和累赘的转换逻辑,让开发更简单。

43
dict-trans-core/pom.xml Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans</artifactId>
<version>${revision}</version>
</parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans-core</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
<optional>true</optional>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- log输出 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,7 +1,6 @@
package com.aizuda.trans.annotation;
import com.aizuda.trans.service.Translatable;
import com.baomidou.mybatisplus.annotation.TableName;
import java.lang.annotation.*;
@ -11,15 +10,15 @@ import java.lang.annotation.*;
* @author luozhan
* @date 2019-03
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dictionary {
/**
* 字典表名
* <p>
* 为空时取 {@link TableName#value()}取不到则取 类名驼峰转大写下划线
* 为空时取 mybatis-plus TableName 取不到则取 类名驼峰转大写下划线
* </p>
*/
String table() default "";

View File

@ -10,9 +10,9 @@ import java.lang.annotation.*;
* @author luozhan
* @date 2019-03
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Translate {
/**

View File

@ -8,14 +8,9 @@ import java.lang.annotation.*;
* @author luozhan
* @date 2020-04
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface Translator {
/**
* 指定翻译配置类可传多个一般为实体class
*/
Class<?>[] value() default void.class;
}

View File

@ -3,6 +3,7 @@ package com.aizuda.trans.util;
import com.aizuda.trans.enums.FormatType;
import java.util.List;
import java.util.stream.Collectors;
/**
* 名字工具类
@ -42,16 +43,13 @@ public class NameUtil {
/**
* 解析驼峰
*
* @param params 参数个数
* @param paramList 参数个数
* @param type 类型
* @return {@link List }<{@link String }>
* @author nn200433
*/
public static List<String> parseCamelTo(List<String> params, FormatType type) {
for (String param : params) {
param = parseCamelTo(param, type);
}
return params;
public static List<String> parseCamelTo(List<String> paramList, final FormatType type) {
return paramList.stream().map(s -> parseCamelTo(s, type)).collect(Collectors.toList());
}
}

70
dict-trans-demo/pom.xml Normal file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans</artifactId>
<version>${revision}</version>
</parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans-demo</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- 核心 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -2,7 +2,6 @@ package com.aizuda.trans;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
/**
@ -12,7 +11,6 @@ import org.springframework.context.annotation.Import;
* @date 2022-12-16 11:30:52
*/
@SpringBootApplication
@Import(cn.hutool.extra.spring.SpringUtil.class)
public class TranslatorBootApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,28 @@
package com.aizuda.trans.controller;
import com.aizuda.trans.annotation.Translator;
import com.aizuda.trans.demo.DemoService;
import com.aizuda.trans.entity.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private DemoService demoService;
@GetMapping("test")
@Translator
public Result test(HttpServletResponse response) {
return Result.builder().status(200).data(demoService.responseNestedMock()).build();
}
}

View File

@ -1,9 +1,6 @@
package com.aizuda.trans.demo;
import com.aizuda.trans.entity.Device;
import com.aizuda.trans.entity.People;
import com.aizuda.trans.entity.People2;
import com.aizuda.trans.entity.People3;
import com.aizuda.trans.entity.*;
import java.util.List;
@ -47,4 +44,20 @@ public interface DemoService {
*/
public List<People3> jsonDemo();
/**
* 响应嵌套模拟
*
* @return {@link List }<{@link People }>
* @author nn200433
*/
public List<People> responseNestedMock();
/**
* 响应嵌套模拟
*
* @return {@link Result }
* @author nn200433
*/
public Result responseNestedMockTest();
}

View File

@ -3,10 +3,7 @@ package com.aizuda.trans.demo.impl;
import cn.hutool.core.collection.CollUtil;
import com.aizuda.trans.annotation.Translator;
import com.aizuda.trans.demo.DemoService;
import com.aizuda.trans.entity.Device;
import com.aizuda.trans.entity.People;
import com.aizuda.trans.entity.People2;
import com.aizuda.trans.entity.People3;
import com.aizuda.trans.entity.*;
import org.springframework.stereotype.Component;
import java.util.List;
@ -52,4 +49,23 @@ public class DemoServiceImpl implements DemoService {
return CollUtil.newArrayList(man, woman);
}
@Override
public List<People> responseNestedMock() {
// 设备
Device man_d1 = Device.builder().status("1").build();
Device woman_d2 = Device.builder().status("2").build();
//
People man = People.builder().sex("1").id("1").phone("18612345678").device(man_d1).build();
People woman = People.builder().sex("2").id("2").phone("18612345678").device(woman_d2).build();
// 丢到响应数据里头响应的真实数据由 ResultUnWrapper 类获取到
return CollUtil.newArrayList(man, woman);
}
@Translator
@Override
public Result responseNestedMockTest() {
return Result.builder().status(200).data(responseNestedMock()).build();
}
}

View File

@ -0,0 +1,21 @@
package com.aizuda.trans.demo.impl;
import com.aizuda.trans.entity.Result;
import com.aizuda.trans.service.UnWrapper;
import org.springframework.stereotype.Component;
/**
* 结果解包器
*
* @author nn200433
* @date 2023-05-25 025 14:48:43
*/
@Component
public class ResultUnWrapper<T> implements UnWrapper<Result<T>> {
@Override
public Object unWrap(Result<T> source) {
return source.getData();
}
}

View File

@ -2,6 +2,7 @@ package com.aizuda.trans.entity;
import com.aizuda.trans.annotation.Dictionary;
import com.aizuda.trans.annotation.Translate;
import com.aizuda.trans.annotation.Translator;
import com.aizuda.trans.constants.DesensitizedTypeConstants;
import com.aizuda.trans.demo.impl.CustomerTranslateServiceImpl;
import com.aizuda.trans.desensitized.Desensitized;
@ -37,4 +38,7 @@ public class People {
private String name;
@Translator
private Device device;
}

View File

@ -7,7 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* 2
*
* @author nn200433
* @date 2022-12-16 016 11:40:30

View File

@ -0,0 +1,26 @@
package com.aizuda.trans.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 响应结果
*
* @author nn200433
* @date 2023-05-25 025 14:47:50
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<R> {
/** 状态 */
private Integer status;
/** 数据 */
private R data;
}

View File

@ -1,11 +1,9 @@
package com.aizuda.trans;
import cn.hutool.core.lang.Console;
import cn.hutool.json.JSONUtil;
import com.aizuda.trans.demo.DemoService;
import com.aizuda.trans.entity.Device;
import com.aizuda.trans.entity.People;
import com.aizuda.trans.entity.People2;
import com.aizuda.trans.entity.People3;
import com.aizuda.trans.entity.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@ -30,25 +28,31 @@ public class TranslatorTest {
@Test
public void demo1() {
List<People> peopleList = demoService.dictDemo();
Console.log("---> 字典 & 脱敏 翻译结果:{}", peopleList);
Console.log("---> 字典 & 脱敏 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo2() {
List<Device> deviceList = demoService.enumDemo();
Console.log("---> 枚举 翻译结果:{}", deviceList);
Console.log("---> 枚举 翻译结果:{}", JSONUtil.toJsonStr(deviceList));
}
@Test
public void demo3() {
List<People2> peopleList = demoService.dbDemo();
Console.log("---> 数据库 翻译结果:{}", peopleList);
Console.log("---> 数据库 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo4() {
List<People3> peopleList = demoService.jsonDemo();
Console.log("---> json 翻译结果:{}", peopleList);
Console.log("---> json 翻译结果:{}", JSONUtil.toJsonStr(peopleList));
}
@Test
public void demo5() {
Result result = demoService.responseNestedMockTest();
Console.log("---> 响应嵌套数据:{}", JSONUtil.toJsonStr(result));
}
}

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans</artifactId>
<version>${revision}</version>
</parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans-spring-boot-starter</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 核心包 -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- AOP拦截 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,43 @@
package com.aizuda.trans.aspect;
import com.aizuda.trans.handler.TranslatorHandle;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.stereotype.Component;
/**
* 字典翻译 在方法上定义@Tanslator注解对方法返回值进行翻译
*
* @author nn200433
* @date 2023-05-25 04:21:27
*/
@Slf4j
@Aspect
@Component
public class TranslateAspect {
@Autowired
private GenericConversionService genericConversionService;
@Pointcut("@annotation(com.aizuda.trans.annotation.Translator)")
public void pointCut() {
}
@AfterReturning(pointcut = "pointCut()", returning = "returnValue")
public void doAfter(JoinPoint joinPoint, Object returnValue) {
if (null == returnValue) {
return;
}
// MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// Translator config = signature.getMethod().getAnnotation(Translator.class);
// 先从容器中获取的转换器进行返回值解包注意此处返回结果可能是Bean也可能是集合然后再进行翻译
TranslatorHandle.transform(genericConversionService.convert(returnValue, Object.class));
}
}

View File

@ -2,9 +2,11 @@ package com.aizuda.trans.config;
import com.aizuda.trans.service.DictTranslateService;
import com.aizuda.trans.service.impl.DefaultDictTranslateServiceImpl;
import com.aizuda.trans.service.impl.wrapper.IPageUnWrapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 翻译配置
@ -13,12 +15,30 @@ import org.springframework.context.annotation.Configuration;
* @date 2022-08-18 018 16:37:42
*/
@Configuration
@Import(cn.hutool.extra.spring.SpringUtil.class)
public class TranslatorConfig {
/**
* 注册字典翻译服务默认实现
*
* @return {@link DictTranslateService }
* @author nn200433
*/
@Bean
@ConditionalOnMissingBean
public DictTranslateService dictTranslateService() {
return new DefaultDictTranslateServiceImpl();
}
/**
* 注册IPage解包器
*
* @return {@link IPageUnWrapper }<{@link Object }>
* @author nn200433
*/
@Bean
public IPageUnWrapper<Object> iPageUnWrapper() {
return new IPageUnWrapper<Object>();
}
}

View File

@ -1,5 +1,6 @@
package com.aizuda.trans.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
@ -8,6 +9,7 @@ import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.aizuda.trans.annotation.Dictionary;
import com.aizuda.trans.annotation.Translate;
import com.aizuda.trans.annotation.Translator;
import com.aizuda.trans.desensitized.IDesensitized;
import com.aizuda.trans.dict.DictTranslate;
import com.aizuda.trans.enums.FormatType;
@ -16,8 +18,6 @@ import com.aizuda.trans.json.IJsonConvert;
import com.aizuda.trans.service.Translatable;
import com.aizuda.trans.service.impl.*;
import com.aizuda.trans.util.NameUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import sun.reflect.annotation.AnnotationType;
@ -47,107 +47,65 @@ public class TranslatorHandle {
* 翻译Map或Entity或Page
*
* @param origin 需要翻译的数据
* @param clazz 指定翻译的模板class可传多个
* @param <T> Map或Entity
* @return origin
*/
public static <T> T parse(T origin, Class<?>... clazz) {
public static <T> T transform(T origin) {
if (origin == null) {
return null;
}
if (origin instanceof List) {
return (T) parse((List) origin, clazz);
if (origin instanceof Collection) {
return (T) parse((Collection) origin);
} else if (origin instanceof Map) {
Map<String, Object> m = (Map<String, Object>) origin;
Collection<Object> values = m.values();
// 查询不为基本类型的数量
long count = values.stream().filter(v -> !ClassUtil.isSimpleTypeOrArray(ClassUtil.getClass(v))).count();
final long count = values.stream().filter(v -> !ClassUtil.isSimpleTypeOrArray(ClassUtil.getClass(v))).count();
if (values.size() == count) {
for (Map.Entry<String, Object> entry : m.entrySet()) {
for (final Map.Entry<String, Object> entry : m.entrySet()) {
Object value = entry.getValue();
// 跳过空对象
if (ObjectUtil.isNull(value)) {
if (ObjUtil.isNull(value)) {
continue;
}
Class<Object> classType = ClassUtil.getClass(value);
value = parse(Collections.singletonList(value), classType).get(0);
value = CollUtil.getFirst(parse(Collections.singletonList(value)));
}
return (T) m;
}
} else if (origin instanceof IPage) {
Page p = (Page) origin;
p.setRecords(parse(p.getRecords(), clazz));
return (T) p;
}
return parse(Collections.singletonList(origin), clazz).get(0);
return CollUtil.getFirst(parse(Collections.singletonList(origin)));
}
/**
* 翻译集合 集合元素为Map或Entity
* <p>
* 注意 如果不指定class默认使用List中元素的class上配置的翻译规则进行翻译 当List集合元素不为Entity类型时class参数至少需要指定1个
*
* @param origins 待翻译数据集合
* @param classes 配置了翻译规则的Entity类型可传多个
* @param originList 待翻译数据集合
* @param <T> 支持Entity或者Map
* @return List
*/
public static <T> List<T> parse(List<T> origins, Class<?>... classes) {
if (CollUtil.isEmpty(origins)) {
return origins;
public static <T> Collection<T> parse(Collection<T> originList) {
if (CollUtil.isEmpty(originList)) {
return originList;
}
// 源数据中属性的格式大写下划线小写下划线驼峰
final FormatType fieldFormatType = getFieldType(origins);
// 获取数据类型
classes = (classes.length != 0 && classes[0] != void.class) ? classes : new Class[]{origins.get(0).getClass()};
// 获取类中需要翻译的属性
final List<Field> translateFieldList = Stream.of(classes)
.map(c -> ReflectUtil.getFields(c))
.flatMap(Stream::of)
.filter(field -> field.isAnnotationPresent(Translate.class))
.collect(Collectors.toList());
final FormatType fieldFormatType = getFieldType(originList);
for (Field field : translateFieldList) {
// 1.获取要翻译的属性名
final String fName = field.getName();
final String fieldName = NameUtil.parseCamelTo(fName, fieldFormatType);
// 2.获取每个待翻译属性的配置
final Translate translateConfig = field.getAnnotation(Translate.class);
final Class<?> dictClass = getDictClass(translateConfig);
final List<String> writeFieldList = NameUtil.parseCamelTo(getTranslateFieldName(translateConfig, fName),
fieldFormatType);
final String groupValue = translateConfig.groupValue();
final String conditionField = translateConfig.conditionField();
final String desensitizedModel = translateConfig.desensitizedModel();
final Dictionary dictionaryConfig = handle(dictClass, translateConfig);
final boolean isJsonConvert = IJsonConvert.class.isAssignableFrom(dictClass);
// 翻译数据
originList.forEach(bean -> {
final Class<?> beanClass = bean.getClass();
final Field[] declaredFields = ReflectUtil.getFields(beanClass);
// 循环处理需要转换的字段字段上的注解链上需要有@Transform且字段类型必须为String
Arrays.stream(declaredFields)
// 只转换String类型的属性其他类型的属性代表是嵌套情况需要过滤掉后面处理
.filter(field -> field.getType() == String.class && AnnotationUtil.hasAnnotation(field, Translate.class))
.forEach(field -> transformField(bean, fieldFormatType, field));
// 转换嵌套字段字段上需要标注@Transform且字段类型不为String递归转换
Arrays.stream(declaredFields)
.filter(field -> field.getType() != String.class && field.isAnnotationPresent(Translator.class))
.forEach(field -> transform(ReflectUtil.invoke(bean, ReflectUtil.getMethodByName(beanClass, "get" + StrUtil.upperFirst(field.getName())))));
});
for (T origin : origins) {
// 获取未翻译的原值如果值为空则跳过
String originValue = Convert.toStr(getProperty(origin, fieldName));
if (originValue == null) {
continue;
}
// 获取条件字段值
String conditionFieldValue = null;
if (StrUtil.isNotBlank(conditionField)) {
conditionFieldValue = Convert.toStr(getProperty(origin, conditionField));
}
// 获取翻译结果并脱敏处理
List<Object> translateValueList = parse(originValue, dictionaryConfig, dictClass, groupValue, conditionFieldValue);
if (!isJsonConvert) {
// 不是 JsonConvert.class 时可进行脱敏
translateValueList = desensitizedHandle(desensitizedModel, translateValueList);
}
// 将翻译结果填入翻译展示字段
setProperty(origin, isJsonConvert, fieldName, writeFieldList, translateValueList);
}
}
return origins;
return originList;
}
/**
@ -171,6 +129,51 @@ public class TranslatorHandle {
Collections.emptyList());
}
/**
* 翻译字段
*
* @param origin
* @param fieldFormatType 字段格式类型
* @param field 字段
* @author nn200433
*/
private static <T> void transformField(T origin, FormatType fieldFormatType, Field field) {
// 1.获取要翻译的属性名
final String fName = field.getName();
final String fieldName = NameUtil.parseCamelTo(fName, fieldFormatType);
// 2.获取每个待翻译属性的配置
final Translate translateConfig = field.getAnnotation(Translate.class);
final Class<?> dictClass = getDictClass(translateConfig);
final List<String> writeFieldList = NameUtil.parseCamelTo(getTranslateFieldName(translateConfig, fName), fieldFormatType);
final String groupValue = translateConfig.groupValue();
final String conditionField = translateConfig.conditionField();
final String desensitizedModel = translateConfig.desensitizedModel();
final Dictionary dictionaryConfig = handle(dictClass, translateConfig);
final boolean isJsonConvert = IJsonConvert.class.isAssignableFrom(dictClass);
// 获取未翻译的原值如果值为空则跳过
String originValue = Convert.toStr(getProperty(origin, fieldName));
if (originValue == null) {
return;
}
// 获取条件字段值
String conditionFieldValue = null;
if (StrUtil.isNotBlank(conditionField)) {
conditionFieldValue = Convert.toStr(getProperty(origin, conditionField));
}
// 获取翻译结果并脱敏处理
List<Object> translateValList = parse(originValue, dictionaryConfig, dictClass, groupValue, conditionFieldValue);
if (!isJsonConvert) {
// 不是 JsonConvert.class 时可进行脱敏
translateValList = desensitizedHandle(desensitizedModel, translateValList);
}
// 将翻译结果填入翻译展示字段
setProperty(origin, isJsonConvert, fieldName, writeFieldList, translateValList);
}
/**
* 判断源数据中的属性格式类型
*
@ -178,8 +181,8 @@ public class TranslatorHandle {
* @return {@link FormatType }
* @author nn200433
*/
private static <T> FormatType getFieldType(List<T> origins) {
T element = origins.get(0);
private static <T> FormatType getFieldType(Collection<T> origins) {
final T element = CollUtil.getFirst(origins);
if (Map.class.isAssignableFrom(element.getClass())) {
Set<String> keySet = ((Map) element).keySet();
for (String key : keySet) {

View File

@ -0,0 +1,36 @@
package com.aizuda.trans.service;
import lombok.NonNull;
import org.springframework.core.convert.converter.Converter;
/**
* 解包器
*
* @author nn200433
* @date 2023-05-25 025 09:48:33
*/
public interface UnWrapper<T> extends Converter<T, Object> {
/**
* 解包
*
* @param source
* @return {@link Object } 包装类内的实际对象
* @author nn200433
*/
public Object unWrap(T source);
/**
* 将类型的 T 源对象转换为目标类型 Object
*
* @param source 要转换的源对象它必须是非 null T 实例
* @return {@link Object } 转换后的对象它是 可能为 null Object
* @author nn200433
*/
@Override
public default Object convert(@NonNull T source) {
return unWrap(source);
}
}

View File

@ -8,13 +8,13 @@ import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.db.handler.EntityHandler;
import cn.hutool.db.handler.EntityListHandler;
import cn.hutool.extra.spring.SpringUtil;
import com.aizuda.trans.annotation.Dictionary;
import com.aizuda.trans.enums.FormatType;
import com.aizuda.trans.service.Translatable;
import com.aizuda.trans.util.NameUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@ -39,7 +39,8 @@ public class DataBaseTranslator implements Translatable {
/**
* 获取数据源
*/
private static DataSource dataSource = SpringUtil.getBean(DataSource.class);
@Autowired(required = false)
private DataSource dataSource;
@Override
public List<Object> translate(String groupValue, String conditionValue, String origin, Dictionary dictConfig,

View File

@ -0,0 +1,19 @@
package com.aizuda.trans.service.impl.wrapper;
import com.aizuda.trans.service.UnWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
* mybatis-plus IPage 解包器
*
* @author nn200433
* @date 2023-05-25 025 10:02:45
*/
public class IPageUnWrapper<T> implements UnWrapper<IPage<T>> {
@Override
public Object unWrap(IPage<T> source) {
return source.getRecords();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 113 KiB

85
pom.xml
View File

@ -5,23 +5,30 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.13</version>
<version>2.7.12</version>
</parent>
<groupId>com.aizuda</groupId>
<artifactId>dict-trans</artifactId>
<packaging>jar</packaging>
<version>0.2</version>
<packaging>pom</packaging>
<version>${revision}</version>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>dict-trans-core</module>
<module>dict-trans-spring-boot-starter</module>
<module>dict-trans-demo</module>
</modules>
<properties>
<!-- 版本 -->
<revision>0.3</revision>
<!-- 数据库操作 -->
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<!-- hutool -->
<hutool.version>5.8.8</hutool.version>
<hutool.version>5.8.18</hutool.version>
<!-- mysql -->
<mysql.version>5.1.49</mysql.version>
<!-- maven插件 -->
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
@ -29,37 +36,17 @@
<maven-javadoc-plugin.version>2.9.1</maven-javadoc-plugin.version>
<nexus-staging-maven-plugin.version>1.6.7</nexus-staging-maven-plugin.version>
<maven-gpg-plugin.version>1.5</maven-gpg-plugin.version>
<flatten-maven-plugin.version>1.2.7</flatten-maven-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- AOP拦截 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis-plus 工具 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
<optional>true</optional>
</dependency>
<!-- hutool工具类 -->
@ -67,17 +54,18 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
<optional>true</optional>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<optional>true</optional>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 发布中央仓所需信息 -->
<name>dict-trans</name>
<description>简单的字典翻译组件</description>
@ -145,6 +133,12 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<!-- 版本占位符 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
</plugin>
<!-- 中央仓相关 -->
<plugin>
<groupId>org.sonatype.plugins</groupId>
@ -213,6 +207,31 @@
</execution>
</executions>
</plugin>
<!-- 处理版本占位符 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- gpg 签名 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -1,40 +0,0 @@
package com.aizuda.trans.aspect;
import com.aizuda.trans.annotation.Translator;
import com.aizuda.trans.handler.TranslatorHandle;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* 字典翻译
* 在方法上定义@Tanslator注解对方法返回值进行翻译
*
* @author luozhan
* @create 2020-04
* @see Translator
*/
@Slf4j
@Aspect
@Component
public class TranslateAspect {
@Pointcut("@annotation(com.aizuda.trans.annotation.Translator)")
public void pointCut() {
}
@AfterReturning(pointcut = "pointCut()", returning = "object")
public void doAfter(JoinPoint joinPoint, Object object) {
if (null == object) {
return;
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Translator config = signature.getMethod().getAnnotation(Translator.class);
TranslatorHandle.parse(object, config.value());
}
}