소스 검색

Merge pull request #74 from hs-web/3.0.0-RC

3.0.0 rc
zhōuhào 7 년 전
부모
커밋
b91c2fef08
100개의 변경된 파일1454개의 추가작업 그리고 712개의 파일을 삭제
  1. 2 0
      .gitignore
  2. 3 4
      FEATURES.md
  3. 3 4
      ISSUE_TEMPLATE.md
  4. 2 3
      README.md
  5. 0 58
      docs/dev-guide/README.md
  6. 0 1
      docs/dev-guide/autz/README.md
  7. 0 79
      docs/dev-guide/crud/README.md
  8. 0 0
      docs/dev-guide/crud/custom-field.md
  9. 0 63
      docs/dev-guide/logging/README.md
  10. 0 6
      docs/help/submit-code-issues.md
  11. BIN
      docs/hsweb3.png
  12. 3 3
      hsweb-authorization/README.md
  13. 1 1
      hsweb-authorization/hsweb-authorization-api/pom.xml
  14. 1 1
      hsweb-authorization/hsweb-authorization-basic/pom.xml
  15. 0 43
      hsweb-authorization/hsweb-authorization-cloud/pom.xml
  16. 0 45
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/AuthorizationClientAutoConfiguration.java
  17. 0 12
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/AuthorizationServerAutoConfiguration.java
  18. 0 23
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/EnableAuthorizationClient.java
  19. 0 17
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/EnableAuthorizationServer.java
  20. 0 19
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignAuthenticationManager.java
  21. 0 13
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignAuthorizationAutoConfiguration.java
  22. 0 73
      hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignUserTokenManager.java
  23. 1 1
      hsweb-authorization/hsweb-authorization-jwt/pom.xml
  24. 0 27
      hsweb-authorization/hsweb-authorization-ldap/pom.xml
  25. 0 17
      hsweb-authorization/hsweb-authorization-ldap/src/test/java/org/hswebframework/web/authorization/ldap/LdapAuthorizationTests.java
  26. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-auth-server/pom.xml
  27. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-client/pom.xml
  28. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-core/pom.xml
  29. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/pom.xml
  30. 1 3
      hsweb-authorization/pom.xml
  31. 1 1
      hsweb-boost/hsweb-boost-aop/pom.xml
  32. 1 1
      hsweb-boost/hsweb-boost-ftp/pom.xml
  33. 1 1
      hsweb-boost/hsweb-boost-validator/hsweb-boost-validator-api/pom.xml
  34. 1 1
      hsweb-boost/hsweb-boost-validator/hsweb-boost-validator-group/pom.xml
  35. 1 1
      hsweb-boost/hsweb-boost-validator/pom.xml
  36. 1 1
      hsweb-boost/pom.xml
  37. 3 1
      hsweb-commons/hsweb-commons-bean/pom.xml
  38. 2 1
      hsweb-commons/hsweb-commons-controller/pom.xml
  39. 2 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-api/pom.xml
  40. 2 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/pom.xml
  41. 2 2
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/EnumDictHandlerRegister.java
  42. 39 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/MybatisDaoAutoConfiguration.java
  43. 61 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/AbstractSqlTermCustomer.java
  44. 16 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/ChangedTermValue.java
  45. 1 3
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/EnumDicInTermTypeMapper.java
  46. 1 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/EnumDicTermTypeMapper.java
  47. 13 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/SqlTermCustomer.java
  48. 61 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/TreeStructureSqlTermCustomer.java
  49. 2 1
      hsweb-commons/hsweb-commons-dao/pom.xml
  50. 1 1
      hsweb-commons/hsweb-commons-entity/pom.xml
  51. 7 0
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/GenericEntity.java
  52. 5 0
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/SimpleGenericEntity.java
  53. 15 5
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/factory/MapperEntityFactory.java
  54. 1 1
      hsweb-commons/hsweb-commons-model/pom.xml
  55. 2 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-api/pom.xml
  56. 3 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-oauth2/pom.xml
  57. 3 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/pom.xml
  58. 3 1
      hsweb-commons/hsweb-commons-service/pom.xml
  59. 2 1
      hsweb-commons/hsweb-commons-utils/pom.xml
  60. 2 1
      hsweb-commons/pom.xml
  61. 1 1
      hsweb-concurrent/hsweb-concurrent-async-job/pom.xml
  62. 1 1
      hsweb-concurrent/hsweb-concurrent-cache/pom.xml
  63. 1 1
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/pom.xml
  64. 1 1
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/pom.xml
  65. 1 1
      hsweb-concurrent/hsweb-concurrent-counter/pom.xml
  66. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-api/pom.xml
  67. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-redis/pom.xml
  68. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-starter/pom.xml
  69. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/pom.xml
  70. 1 1
      hsweb-concurrent/pom.xml
  71. 22 1
      hsweb-core/README.md
  72. 2 2
      hsweb-core/pom.xml
  73. 6 0
      hsweb-core/src/main/java/org/hswebframework/web/bean/BeanFactory.java
  74. 1 0
      hsweb-core/src/main/java/org/hswebframework/web/bean/Copier.java
  75. 342 0
      hsweb-core/src/main/java/org/hswebframework/web/bean/DefaultToStringOperator.java
  76. 42 10
      hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java
  77. 134 0
      hsweb-core/src/main/java/org/hswebframework/web/bean/ToString.java
  78. 17 0
      hsweb-core/src/main/java/org/hswebframework/web/bean/ToStringOperator.java
  79. 121 12
      hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java
  80. 4 3
      hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java
  81. 12 6
      hsweb-core/src/test/java/org/hswebframework/web/bean/NestObject.java
  82. 2 2
      hsweb-core/src/test/java/org/hswebframework/web/bean/Source.java
  83. 20 6
      hsweb-core/src/test/java/org/hswebframework/web/bean/Target.java
  84. 19 7
      hsweb-core/src/test/java/org/hswebframework/web/dict/DictDefineTest.java
  85. 6 0
      hsweb-core/src/test/java/org/hswebframework/web/dict/UserCode.java
  86. 14 0
      hsweb-datasource/README.md
  87. 3 1
      hsweb-datasource/hsweb-datasource-api/pom.xml
  88. 62 31
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/AopDataSourceSwitcherAutoConfiguration.java
  89. 1 1
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/annotation/UseDataSource.java
  90. 47 0
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/AnnotationDataSourceSwitchStrategyMatcher.java
  91. 66 0
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/CachedDataSourceSwitchStrategyMatcher.java
  92. 60 0
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/DataSourceSwitchStrategyMatcher.java
  93. 67 0
      hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/ExpressionDataSourceSwitchStrategyMatcher.java
  94. 3 1
      hsweb-datasource/hsweb-datasource-jta/pom.xml
  95. 0 1
      hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/AtomikosDataSourceConfig.java
  96. 12 14
      hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/InMemoryAtomikosDataSourceRepository.java
  97. 45 49
      hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/JtaDynamicDataSourceService.java
  98. 30 3
      hsweb-datasource/hsweb-datasource-jta/src/test/java/org/hswebframework/web/datasource/jta/SimpleAtomikosTests.java
  99. 8 2
      hsweb-datasource/hsweb-datasource-jta/src/test/resources/application.yml
  100. 0 0
      hsweb-datasource/hsweb-datasource-web/pom.xml

+ 2 - 0
.gitignore

@@ -19,3 +19,5 @@
 # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
 hs_err_pid*
 **/transaction-logs/
+pom.xml.versionsBackup
+build/

+ 3 - 4
FEATURES.md

@@ -12,13 +12,12 @@ hsweb3.0 是模块化的,每个功能可独立使用,可选择自己需要的功
 + [x] OAuth2.0 服务和客户端,支持使用OAuth2.0进行单点登录
 + [x] 数据字典功能,支持自定义字典解析器,满足特定字典格式需求.
 + [x] 菜单管理.支持菜单分组,灵活配置菜单结构.
-+ [ ] 动态脚本.在线编写脚本任务,维护系统更灵活.
++ [x] 动态脚本.在线编写脚本任务,维护系统更灵活.
 + [x] 动态表单.在线设计表单(需前端支持),提供统一CRUD接口,随建随用.
 + [ ] 工作流引擎.配合动态表单,实现工作流灵活自定义.
 + [x] ***组织架构.采用[地区-组织-部门-职位-人员]方式,支持灵活的数据权限控制.***
 + [x] 定时调度.在线配置任务,使用动态脚本编写任务内容,维护更方便.
 + [x] 文件管理. 文件上传下载统一接口,支持文件秒传.
 + [x] 访问日志. 记录用户每次访问信息.
-+ [ ] 历史记录. 记录数据修改记录.
-+ [ ] 在线数据库管理. 在线维护数据库,执行sql等操作.
-+ [ ] 代码生成器.可自定义模板,各种项目结构随心所欲.
++ [x] 在线数据库管理. 在线维护数据库,执行sql等操作.
++ [x] 代码生成器.可自定义模板,各种项目结构随心所欲.

+ 3 - 4
ISSUE_TEMPLATE.md

@@ -1,4 +1,3 @@
-
-1. Bugs,如果你发现了项目中存在bug,请详细描述bug存在的位置,复现的条件,以及bug引起的问题.如果可能,尽量提供复现代码.
-2. 新特性,如果你觉得项目中的某些功能不够好,或者不能满足您的需求,你可以对需求进行详细描述,我会酌情改进.
-3. 项目中遇到疑问,请确定已经看过wiki中的内容,并且在issues中未找到你需要的答案,你可以加入qq群`515649185`询问或者直接提issue.
+1. 问题描述:
+2. 复现步骤:
+3. 日志内容:

+ 2 - 3
README.md

@@ -38,9 +38,8 @@
 |[hsweb-concurrent](hsweb-concurrent)|并发包,缓存,锁,计数器等| 80%|
 |[hsweb-core](hsweb-core)|框架核心,基础工具类| 90%|
 |[hsweb-datasource](hsweb-datasource)|数据源| 90%|
-|[hsweb-examples](hsweb-examples)|例子,演示| 10%|
 |[hsweb-logging](hsweb-logging)| 日志|  100%|
 |[hsweb-message](hsweb-message)|mq,websocket...| 80%|
 |[hsweb-starter](hsweb-starter)|模块启动器| 90%|
-|[hsweb-system](hsweb-system)|**系统常用功能**| 60%|
-|[hsweb-tests](hsweb-tests)|测试| 80%|
+|[hsweb-system](hsweb-system)|**系统常用功能**| 80%|
+|[hsweb-thirdparty](hsweb-thirdparty)| 第三方插件 | 100% |

+ 0 - 58
docs/dev-guide/README.md

@@ -1,58 +0,0 @@
-# hsweb 开发手册
-
-## 框架基础设施
-主要为框架提供的常用工具
-1. [增删改查](crud)
-    * [通用增删改查](crud#通用增删改查)
-    * [动态条件](crud#动态条件)
-    * [表关联动态条件](crud#表关联)
-    * [拓展自定义字段](crud/custom-field.md)
-2. 权限控制
-    * 常用API
-    * 使用注解声明权限控制
-    * 自定义声明权限控制
-    * 拓展数据权限控制
-3. [访问日志](logging)
-    * [声明记录访问日志](logging#声明记录访问日志)
-    * [监听访问日志](logging#监听访问日志)
-    * [日志序列化](logging#日志序列化)
-4. 动态数据源
-    * 在配置文件中添加动态数据源
-    * 通过自定义,在数据库或其他地方添加动态数据源
-    * 注解方式切换动态数据源
-    * 编程方式切换动态数据源
-5. 常用并发工具
-    * 锁,分布式锁
-    * 计数器
-    * 异步任务,批量任务,事务
-6. 消息封装
-    * 消息队列
-    * websocket
-7. 其他工具
-    * 智能日期格式化
-    * excel,word操作
-    * 动态脚本引擎
-
-## 系统功能
-主要为框架实现的常用功能
-1. 权限配置
-2. 组织架构
-    * 组织架构数据权限控制
-    * 人员关系
-3. 数据字典
-4. 动态表单
-   * 设计表单
-   * 增删改查
-   * 验证器
-   * 触发器
-5. 文件上传下载
-    * 本地文件上传下载
-    * 文件秒传
-    * 静态文件上传下载
-    * 拓展其他文件上传
-6. 定时调度
-    * 动态脚本编写定时调度任务
-    * 集群下指定固定节点执行任务
-7. 动态脚本
-8. 工作流引擎
-    * flowable工作流设计器

+ 0 - 1
docs/dev-guide/autz/README.md

@@ -1 +0,0 @@
-# 

+ 0 - 79
docs/dev-guide/crud/README.md

@@ -1,79 +0,0 @@
-# 通用增删改查
-hsweb 中提供了一套通用的增删改查封装([hsweb-commons](../../../hsweb-commons)),实现增删改查以及动态查询。
-
-接口约定: 
-1. 实体类需要实现`Entity`接口, 通用实体类继承`GenericEntity<主键>`类.
-2. Dao继承`Dao`接口, 通用增删改查继承`CrudDao<实体类,主键>`.
-3. Service继承`Service`接口,通用增删改查继承`CrudService`.
-4. Controller,通用增删改查实现`SimpleGenericEntityController<实体类,主键,动态查询参数>`
-
-实现约定:
-1. 框架提供的实体都是接口形式,使用`EntityFactory`来创建实例,便于拓展属性. 实际业务中,可能并不需要这么做.
-2. Dao通用增删改查目前提供mybatis实现,可参照[UserMapper.xml](https://github.com/hs-web/hsweb-framework/blob/master/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-dao/hsweb-system-authorization-dao-mybatis/src/main/resources/org/hswebframework/web/dao/mybatis/mappers/authorization/UserMapper.xml#L23-L69)
-   使用xml方式,提供了动态条件的同时保留了灵活性.
-3. Service提供了dsl的方式构造动态条件,继承`GenericEntityService<实体类,主键>`即可.注意:框架未使用dao来生成主键,而是在service中通过IDGenerator来生成.
-
-# 动态条件
-
-### Service中使用dsl进行动态条件
-继承`GenericEntityService`后可获得dsl方式动态条件功能:
-
-```java
-    public void method(){
-       //where name=? or name = ?
-       createQuery().where("name","张三").or("name","李四").list();
-       //set status=? where id = ?
-       createUpdate().set("status",1).where("id",id).exec(); //注意需要调用exec()
-    }
-```
-更多用法,详见:[hsweb-commons-service-simple](https://github.com/hs-web/hsweb-framework/tree/master/hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple)
-
-### 前端传参,动态查询
-目前仅实现了一种动态条件从参数:`QueryParamEntity`,
-因此Controller实现`SimpleGenericEntityController<实体类,主键,QueryParamEntity>`接口. 获得动态查询功能
-
-```bash
-  GET /user?terms[0].column=name&terms[0].termType=like&terms[0].value=张三
-  // 等同于 where name like ?
-```
-更多用法,详见:[hsweb-commons-controller](https://github.com/hs-web/hsweb-framework/tree/master/hsweb-commons/hsweb-commons-controller)
-
-# 表关联
-由于动态条件实现较简单,目前动态条件需要修改mybatis dao实现的的mapper.xml,局部代码如下
-```xml
- <resultMap id="CardDataResultMap" type="com.zmcsoft.apsp.api.card.entity.SimpleCardDataEntity">
-        <id property="id" column="u_id" javaType="string" jdbcType="VARCHAR"/>
-        <result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
-        
-        <!--关联表的信息-->
-        <result property="detail.email" column="detail.email" javaType="String" jdbcType="VARCHAR"/>
-        <result property="detail.phoneNumber" column="detail.phone_number" javaType="String" jdbcType="VARCHAR"/>
-</resultMap>
-
- <select id="query" parameterType="org.hswebframework.web.commons.entity.Entity" resultMap="CardDataResultMap">
-        <include refid="config"/>
-        select
-        <include refid="BasicMapper.buildSelectField"/>
-        from user 
-        left join user_detail detail on detail.user_id = user.id
-        <where>
-            <include refid="BasicMapper.buildWhere"/>
-        </where>
-        <include refid="BasicMapper.buildSortField"/>
-</select>
-
-<select id="count" parameterType="org.hswebframework.web.commons.entity.Entity" resultMap="CardDataResultMap">
-        <include refid="config"/>
-        select count(1) from user 
-        left join user_detail detail on detail.user_id = user.id
-        <where>
-            <include refid="BasicMapper.buildWhere"/>
-        </where>
-        <include refid="BasicMapper.buildSortField"/>
-</select>
-```
-
-然后就可以通过动态查询来查询了
-```java
-createQuery().where("detail.email","admin@hsweb.me").single(); 
-```

+ 0 - 0
docs/dev-guide/crud/custom-field.md


+ 0 - 63
docs/dev-guide/logging/README.md

@@ -1,63 +0,0 @@
-## 声明记录访问日志
-
-1. 如果你是maven工程
-    * 引入私服配置
-    
-            <repositories>
-                <repository>
-                    <id>hsweb-nexus</id>
-                    <name>Nexus Release Repository</name>
-                    <url>http://nexus.hsweb.me/content/groups/public/</url>
-                    <snapshots>
-                        <enabled>true</enabled>
-                    </snapshots>
-                </repository>
-            </repositories>
-    
-    * 直接引入依赖
-
-            <dependency>
-                <groupId>org.hswebframework.web</groupId>
-                <artifactId>hsweb-access-logging-aop</artifactId>
-                <version>3.0-SNAPSHOT</version>
-            </dependency>
-		
-	
-2. 如果你是非maven工程,[请自行去以下地址](http://nexus.hsweb.me/)下载jar包
-		
-## 监听访问日志
-
-1. 开启访问日志
-    * 在启动类中注解@EnableAccessLogger
-    
-            @SpringBootApplication
-            @EnableAccessLogger
-            public class AppApplication {
-                public static void main(String[] args) {
-                    SpringApplication.run(AppApplication.class, args);
-                }	
-            }
-2. 访问日志 API
-
-    * controller类或者方法上,注解 @AccessLogger("功能描述")
-    
-            @AccessLogger("hello")
-            @RequestMapping(value = "/",method = RequestMethod.GET)
-            public String  hello() {
-                return "Hello World ! ";
-            }        
-    
-3. 日志监听
-
-    * 创建类,实现: AccessLoggerListener接口并注入到spring容器, 当有日志产生时,会调用接口方法onLogger,并传入日志信息
-    
-            @Component
-            public class MyLoggingListener implements AccessLoggerListener {
-                @Override
-                public void onLogger(AccessLoggerInfo loggerInfo) {
-                    System.out.println(loggerInfo.toString());
-                }
-            }
-    
- 
-## 日志序列化

+ 0 - 6
docs/help/submit-code-issues.md

@@ -1,6 +0,0 @@
-# 提交包含代码的 issues
-
-1. 在github中打开代码,点击行号选择代码(按住shift多选);或者使用idea,选中代码段,右键-open on github.
-2. 右侧出现 ... 的图标,点击,选择open new issue按钮. 或者直接复制浏览器地址栏中的地址到issue中.
-3. 描述问题
-4. 提交

BIN
docs/hsweb3.png


+ 3 - 3
hsweb-authorization/README.md

@@ -3,7 +3,7 @@
 
 # 目录介绍
 1. [hsweb-authorization-api](hsweb-authorization-api):权限控制API
-1. [hsweb-authorization-oauth2](hsweb-authorization-oauth2):oauth2支持
-1. [hsweb-authorization-basic](hsweb-authorization-basic):权限控制基础实现
-1. [hsweb-authorization-jwt](hsweb-authorization-jwt):权限控制jwt拓展
+2. [hsweb-authorization-oauth2](hsweb-authorization-oauth2):oauth2支持
+3. [hsweb-authorization-basic](hsweb-authorization-basic):权限控制基础实现
+4. [hsweb-authorization-jwt](hsweb-authorization-jwt):权限控制jwt拓展
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-basic/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 0 - 43
hsweb-authorization/hsweb-authorization-cloud/pom.xml

@@ -1,43 +0,0 @@
-<?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">
-    <parent>
-        <artifactId>hsweb-authorization</artifactId>
-        <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>hsweb-authorization-cloud</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.hswebframework.web</groupId>
-            <artifactId>hsweb-authorization-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-netflix-core</artifactId>
-            <version>1.3.1.RELEASE</version>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-feign</artifactId>
-            <version>1.3.1.RELEASE</version>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <groupId>org.hswebframework.web</groupId>
-            <artifactId>hsweb-authorization-oauth2-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-            <optional>true</optional>
-        </dependency>
-    </dependencies>
-</project>

+ 0 - 45
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/AuthorizationClientAutoConfiguration.java

@@ -1,45 +0,0 @@
-package org.hswebframework.web.authorization.cloud;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.ImportSelector;
-import org.springframework.core.type.AnnotationMetadata;
-import org.springframework.util.ClassUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author zhouhao
- * @since
- */
-@Configuration
-public class AuthorizationClientAutoConfiguration implements ImportSelector {
-
-    @Override
-    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
-        Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(EnableAuthorizationClient.class.getName());
-
-        EnableAuthorizationClient.Type type = (EnableAuthorizationClient.Type) attrs.get("type");
-        List<String> classNames = new ArrayList<>();
-
-        if (type != null) {
-            switch (type) {
-                case Feign:
-                    classNames.add("org.hswebframework.web.authorization.cloud.client.feign.FeignAutoConfiguration");
-                    break;
-                case Auto:
-                default:
-                    try {
-                        Class.forName("org.springframework.cloud.netflix.feign.FeignClient");
-                        classNames.add("org.hswebframework.web.authorization.cloud.client.feign.FeignAutoConfiguration");
-                    } catch (ClassNotFoundException e) {
-                        // load redis not support yet
-                        throw new UnsupportedOperationException("please import and config feign");
-                    }
-                    break;
-            }
-        }
-        return classNames.toArray(new String[classNames.size()]);
-    }
-}

+ 0 - 12
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/AuthorizationServerAutoConfiguration.java

@@ -1,12 +0,0 @@
-package org.hswebframework.web.authorization.cloud;
-
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author zhouhao
- * @since 3.0
- */
-@Configuration
-public class AuthorizationServerAutoConfiguration {
-
-}

+ 0 - 23
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/EnableAuthorizationClient.java

@@ -1,23 +0,0 @@
-package org.hswebframework.web.authorization.cloud;
-
-import org.springframework.context.annotation.Import;
-
-import java.lang.annotation.*;
-
-/**
- * 启用权限认证客户端
- * @author zhouhao
- * @since 3.0
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-@Documented
-@Import(AuthorizationServerAutoConfiguration.class)
-public @interface EnableAuthorizationClient {
-
-    Type value() default Type.Auto;
-
-    enum Type {
-        Auto, Feign
-    }
-}

+ 0 - 17
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/EnableAuthorizationServer.java

@@ -1,17 +0,0 @@
-package org.hswebframework.web.authorization.cloud;
-
-import org.springframework.context.annotation.Import;
-
-import java.lang.annotation.*;
-
-/**
- * 启用权限认证服务端
- * @author zhouhao
- * @since 3.0
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-@Documented
-@Import(AuthorizationServerAutoConfiguration.class)
-public @interface EnableAuthorizationServer {
-}

+ 0 - 19
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignAuthenticationManager.java

@@ -1,19 +0,0 @@
-package org.hswebframework.web.authorization.cloud.client.feign;
-
-import org.hswebframework.web.authorization.Authentication;
-import org.hswebframework.web.authorization.AuthenticationManager;
-import org.springframework.cloud.netflix.feign.FeignClient;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-
-@FeignClient(name = "${hsweb.cloud.user-center.name:user-center}")
-public interface FeignAuthenticationManager extends AuthenticationManager {
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-auth/{userId}", method = RequestMethod.GET)
-    Authentication getByUserId(@PathVariable("userId") String userId);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-auth", method = RequestMethod.PUT)
-    Authentication sync(Authentication authentication);
-}

+ 0 - 13
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignAuthorizationAutoConfiguration.java

@@ -1,13 +0,0 @@
-package org.hswebframework.web.authorization.cloud.client.feign;
-
-import org.springframework.cloud.netflix.feign.EnableFeignClients;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author zhouhao
- * @since 3.0
- */
-@Configuration
-@EnableFeignClients("org.hswebframework.web.authorization.cloud.client.feign")
-public class FeignAuthorizationAutoConfiguration {
-}

+ 0 - 73
hsweb-authorization/hsweb-authorization-cloud/src/main/java/org/hswebframework/web/authorization/cloud/client/feign/FeignUserTokenManager.java

@@ -1,73 +0,0 @@
-package org.hswebframework.web.authorization.cloud.client.feign;
-
-import org.hswebframework.web.authorization.token.TokenState;
-import org.hswebframework.web.authorization.token.UserToken;
-import org.hswebframework.web.authorization.token.UserTokenManager;
-import org.springframework.cloud.netflix.feign.FeignClient;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-/**
- * @author zhouhao
- * @since 3.0
- */
-@FeignClient(name = "${hsweb.cloud.user-center.name:user-center}")
-public interface FeignUserTokenManager extends UserTokenManager {
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/token/{token}", method = RequestMethod.GET)
-    UserToken getByToken(@PathVariable("token") String token);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/user/{userId}", method = RequestMethod.GET)
-    List<UserToken> getByUserId(@PathVariable("userId") String userId);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/user/{userId}/logged", method = RequestMethod.GET)
-    boolean userIsLoggedIn(@PathVariable("userId") String userId);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/token/{token}/logged", method = RequestMethod.GET)
-    boolean tokenIsLoggedIn(@PathVariable("token") String token);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/user/total", method = RequestMethod.GET)
-    long totalUser();
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/token/total", method = RequestMethod.GET)
-    long totalToken();
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token", method = RequestMethod.GET)
-    List<UserToken> allLoggedUser();
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/user/{userId}", method = RequestMethod.DELETE)
-    void signOutByUserId(@PathVariable("userId") String userId);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/token/{token}", method = RequestMethod.DELETE)
-    void signOutByToken(@PathVariable("token") String token);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/user/{userId}/{state}", method = RequestMethod.PUT)
-    void changeUserState(@PathVariable("userId") String userId, @PathVariable("state") TokenState state);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/token/{token}/{state}", method = RequestMethod.PUT)
-    void changeTokenState(@PathVariable("token") String token, @PathVariable("state") TokenState state);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/{token}/{type}/{userId}/{maxInactiveInterval}", method = RequestMethod.POST)
-    UserToken signIn(@PathVariable("token") String token, @PathVariable("type") String type, @PathVariable("userId") String userId, @PathVariable("maxInactiveInterval") long maxInactiveInterval);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/{token}/touch", method = RequestMethod.GET)
-    void touch(@PathVariable("token") String token);
-
-    @Override
-    @RequestMapping(value = "${hsweb.cloud.user-center.prefix:/}user-token/check-expired-token", method = RequestMethod.PUT)
-    void checkExpiredToken();
-}

+ 1 - 1
hsweb-authorization/hsweb-authorization-jwt/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 0 - 27
hsweb-authorization/hsweb-authorization-ldap/pom.xml

@@ -1,27 +0,0 @@
-<?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">
-    <parent>
-        <artifactId>hsweb-authorization</artifactId>
-        <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>hsweb-authorization-ldap</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.hswebframework.web</groupId>
-            <artifactId>hsweb-authorization-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.ldap</groupId>
-            <artifactId>spring-ldap-core</artifactId>
-            <version>2.3.1.RELEASE</version>
-        </dependency>
-    </dependencies>
-
-</project>

+ 0 - 17
hsweb-authorization/hsweb-authorization-ldap/src/test/java/org/hswebframework/web/authorization/ldap/LdapAuthorizationTests.java

@@ -1,17 +0,0 @@
-package org.hswebframework.web.authorization.ldap;
-
-import org.junit.Test;
-import org.springframework.ldap.core.LdapTemplate;
-import org.springframework.ldap.query.LdapQueryBuilder;
-
-public class LdapAuthorizationTests {
-
-    LdapTemplate ldapTemplate;
-
-   // @Test
-    public void testGetUser(){
-        ldapTemplate=new LdapTemplate();
-
-        ldapTemplate.authenticate(LdapQueryBuilder.query().base("dc=261consulting, dc=com"),"admin");
-    }
-}

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-auth-server/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-client/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-core/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
         <!--<relativePath>../../pom.xml</relativePath>-->
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 3
hsweb-authorization/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -16,8 +16,6 @@
         <module>hsweb-authorization-oauth2</module>
         <module>hsweb-authorization-basic</module>
         <module>hsweb-authorization-jwt</module>
-        <module>hsweb-authorization-cloud</module>
-        <module>hsweb-authorization-ldap</module>
     </modules>
 
 

+ 1 - 1
hsweb-boost/hsweb-boost-aop/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-ftp/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-validator/hsweb-boost-validator-api/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-boost-validator</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-validator/hsweb-boost-validator-group/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-boost-validator</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-validator/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 1
hsweb-commons/hsweb-commons-bean/pom.xml

@@ -5,13 +5,15 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>hsweb-commons-bean</artifactId>
 
     <dependencies>
+
         <dependency>
             <groupId>org.hswebframework.web</groupId>
             <artifactId>hsweb-core</artifactId>

+ 2 - 1
hsweb-commons/hsweb-commons-controller/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-api/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons-dao</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons-dao</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 2
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/EnumDictHandlerRegister.java

@@ -89,7 +89,7 @@ public class EnumDictHandlerRegister {
         @Override
         public void setParameter(PreparedStatement ps, int i, Object[] parameter, JdbcType jdbcType) throws SQLException {
             T[] ts = ((T[]) parameter);
-            ps.setLong(i, EnumDict.toBit(ts));
+            ps.setLong(i, EnumDict.toMask(ts));
         }
 
         @Override
@@ -111,7 +111,7 @@ public class EnumDictHandlerRegister {
             if (null == value) {
                 return null;
             }
-            List<T> ts = EnumDict.getByBit(getType(), value);
+            List<T> ts = EnumDict.getByMask(getType(), value);
             return ts.toArray((Object[]) Array.newInstance(type, ts.size()));
         }
     }

+ 39 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/MybatisDaoAutoConfiguration.java

@@ -18,13 +18,22 @@
 
 package org.hswebframework.web.dao.mybatis;
 
+import org.hswebframework.ezorm.rdb.render.dialect.Dialect;
 import org.hswebframework.web.dao.Dao;
+import org.hswebframework.web.dao.mybatis.mapper.SqlTermCustomer;
 import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
 @Configuration
 @ComponentScan("org.hswebframework.web.dao.mybatis")
 @MapperScan(value = "org.hswebframework.web.dao", markerInterface = Dao.class)
@@ -32,5 +41,35 @@ import org.springframework.context.annotation.Configuration;
 @EnableConfigurationProperties(MybatisProperties.class)
 public class MybatisDaoAutoConfiguration {
 
+    @Bean
+    public BeanPostProcessor SqlTermCustomerRegister() {
+
+        List<Dialect> dialects = Arrays.asList(
+                Dialect.H2
+                , Dialect.MYSQL
+                , Dialect.ORACLE
+                , Dialect.MSSQL);
+
+        return new BeanPostProcessor() {
+            @Override
+            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+                return bean;
+            }
 
+            @Override
+            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+                if (bean instanceof SqlTermCustomer) {
+                    SqlTermCustomer customer = ((SqlTermCustomer) bean);
+                    if (customer.forDialect() != null) {
+                        for (Dialect dialect : customer.forDialect()) {
+                            dialect.setTermTypeMapper(customer.getTermType(), customer);
+                        }
+                    } else {
+                        dialects.forEach(dialect -> dialect.setTermTypeMapper(customer.getTermType(), customer));
+                    }
+                }
+                return bean;
+            }
+        };
+    }
 }

+ 61 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/AbstractSqlTermCustomer.java

@@ -0,0 +1,61 @@
+package org.hswebframework.web.dao.mybatis.mapper;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.meta.RDBColumnMetaData;
+import org.hswebframework.ezorm.rdb.render.SqlAppender;
+import org.hswebframework.ezorm.rdb.render.dialect.Dialect;
+
+import java.util.List;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@AllArgsConstructor
+public abstract class AbstractSqlTermCustomer implements SqlTermCustomer {
+
+    @Getter
+    protected final String termType;
+
+    @Override
+    public Dialect[] forDialect() {
+        return null;
+    }
+
+    protected String createColumnName(RDBColumnMetaData column, String tableAlias) {
+        return column.getTableMetaData()
+                .getDatabaseMetaData()
+                .getDialect()
+                .buildColumnName(tableAlias, column.getName());
+    }
+
+
+    protected ChangedTermValue createChangedTermValue(Term term) {
+        if (term.getValue() instanceof ChangedTermValue) {
+            return ((ChangedTermValue) term.getValue());
+        } else {
+            ChangedTermValue termValue = new ChangedTermValue(term.getValue(), term.getValue());
+            term.setValue(termValue);
+            return termValue;
+        }
+    }
+
+    protected Object appendCondition(List<Object> values, String wherePrefix, SqlAppender appender) {
+        int len = values.size();
+        if (len == 1) {
+            appender.add("=#{", wherePrefix, ".value.value[0]}");
+        } else {
+            appender.add("in(");
+            for (int i = 0; i < len; i++) {
+                if (i > 0) {
+                    appender.add(",");
+                }
+                appender.add("#{", wherePrefix, ".value.value[" + i + "]}");
+            }
+            appender.add(")");
+        }
+        return values;
+    }
+}

+ 16 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/ChangedTermValue.java

@@ -0,0 +1,16 @@
+package org.hswebframework.web.dao.mybatis.mapper;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@Data
+@AllArgsConstructor
+public class ChangedTermValue {
+    private Object old;
+
+    private Object value;
+}

+ 1 - 3
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/EnumDicInTermTypeMapper.java

@@ -3,14 +3,12 @@ package org.hswebframework.web.dao.mybatis.mapper;
 import lombok.AllArgsConstructor;
 import org.hswebframework.ezorm.core.param.Term;
 import org.hswebframework.ezorm.rdb.meta.RDBColumnMetaData;
-import org.hswebframework.ezorm.rdb.render.Sql;
 import org.hswebframework.ezorm.rdb.render.SqlAppender;
 import org.hswebframework.ezorm.rdb.render.dialect.Dialect;
 import org.hswebframework.ezorm.rdb.render.dialect.Dialect.TermTypeMapper;
 import org.hswebframework.web.dict.EnumDict;
 
 import java.util.*;
-import java.util.stream.Collectors;
 
 @AllArgsConstructor
 @SuppressWarnings("all")
@@ -45,7 +43,7 @@ public abstract class EnumDicInTermTypeMapper implements TermTypeMapper {
                             .stream().map(v -> EnumDict.find(componentType, v).orElse(null))
                             .filter(Objects::nonNull)
                             .toArray(EnumDict[]::new);
-                    long bit = EnumDict.toBit(newValue);
+                    long bit = EnumDict.toMask(newValue);
                     term.setValue(bit);
                 }else{
                     //枚举数量大于等于64,无法使用位运算

+ 1 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/EnumDicTermTypeMapper.java

@@ -41,7 +41,7 @@ public class EnumDicTermTypeMapper implements TermTypeMapper {
                         .stream().map(v -> EnumDict.find(componentType, v).orElse(null))
                         .filter(Objects::nonNull)
                         .toArray(EnumDict[]::new);
-                long bit = EnumDict.toBit(newValue);
+                long bit = EnumDict.toMask(newValue);
                 term.setValue(bit);
             }
         } else {

+ 13 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/SqlTermCustomer.java

@@ -0,0 +1,13 @@
+package org.hswebframework.web.dao.mybatis.mapper;
+
+import org.hswebframework.ezorm.rdb.render.dialect.Dialect;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public interface SqlTermCustomer extends Dialect.TermTypeMapper {
+    String getTermType();
+
+    Dialect[] forDialect();
+}

+ 61 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/TreeStructureSqlTermCustomer.java

@@ -0,0 +1,61 @@
+package org.hswebframework.web.dao.mybatis.mapper;
+
+import org.hswebframework.ezorm.core.param.Term;
+import org.hswebframework.ezorm.rdb.meta.RDBColumnMetaData;
+import org.hswebframework.ezorm.rdb.render.SqlAppender;
+import org.hswebframework.ezorm.rdb.render.dialect.term.BoostTermTypeMapper;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public abstract class TreeStructureSqlTermCustomer extends AbstractSqlTermCustomer {
+    boolean not = false;
+
+    public TreeStructureSqlTermCustomer(String termType, boolean not) {
+        super(termType);
+        this.not = not;
+    }
+
+    protected abstract String getTableName();
+
+    protected abstract List<String> getTreePathByTerm(List<Object> termValue);
+
+    @Override
+    public SqlAppender accept(String wherePrefix, Term term, RDBColumnMetaData column, String tableAlias) {
+        ChangedTermValue termValue = createChangedTermValue(term);
+
+        List<Object> value = BoostTermTypeMapper.convertList(column, termValue.getOld());
+
+        List<String> paths = getTreePathByTerm(value)
+                .stream()
+                .map(path -> path.concat("%"))
+                .collect(Collectors.toList());
+
+        termValue.setValue(paths);
+
+        SqlAppender termCondition = new SqlAppender();
+
+        termCondition.add(not ? "not " : "", "exists(select 1 from ", getTableName(), " tmp where tmp.u_id = ", createColumnName(column, tableAlias));
+        int len = paths.size();
+
+        if (len > 0) {
+            termCondition.add(" and (");
+        }
+        for (int i = 0; i < len; i++) {
+            if (i > 0) {
+                termCondition.addSpc("or");
+            }
+            termCondition.add("tmp.path like #{", wherePrefix, ".value[", i, "]}");
+        }
+        if (len > 0) {
+            termCondition.add(")");
+        }
+        termCondition.add(")");
+
+        return termCondition;
+    }
+}

+ 2 - 1
hsweb-commons/hsweb-commons-dao/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-commons/hsweb-commons-entity/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 7 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/GenericEntity.java

@@ -17,6 +17,9 @@
  */
 
 package org.hswebframework.web.commons.entity;
+
+import org.hswebframework.web.bean.ToString;
+
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -35,6 +38,10 @@ public interface GenericEntity<PK> extends CloneableEntity {
 
     void setId(PK id);
 
+    default String toString(String... ignoreProperty) {
+        return ToString.toString(this, ignoreProperty);
+    }
+
     default Map<String, Object> getProperties() {
         return null;
     }

+ 5 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/SimpleGenericEntity.java

@@ -35,6 +35,11 @@ public abstract class SimpleGenericEntity<PK> implements GenericEntity<PK> {
 
     private Map<String, Object> properties;
 
+    @Override
+    public String toString() {
+        return toString((String[]) null);
+    }
+
     @Override
     public PK getId() {
         return this.id;

+ 15 - 5
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/factory/MapperEntityFactory.java

@@ -21,6 +21,7 @@ package org.hswebframework.web.commons.entity.factory;
 import lombok.SneakyThrows;
 import org.hswebframework.web.NotFoundException;
 import org.hswebframework.utils.ClassUtils;
+import org.hswebframework.web.bean.BeanFactory;
 import org.hswebframework.web.bean.FastBeanCopier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,10 +35,10 @@ import java.util.function.Supplier;
  * @since 3.0
  */
 @SuppressWarnings("unchecked")
-public class MapperEntityFactory implements EntityFactory {
-    private Map<Class, Mapper> realTypeMapper = new HashMap<>();
-    private Logger logger = LoggerFactory.getLogger(this.getClass());
-    private Map<String, PropertyCopier> copierCache = new HashMap<>();
+public class MapperEntityFactory implements EntityFactory, BeanFactory {
+    private Map<Class, Mapper>          realTypeMapper = new HashMap<>();
+    private Logger                      logger         = LoggerFactory.getLogger(this.getClass());
+    private Map<String, PropertyCopier> copierCache    = new HashMap<>();
 
     private static final DefaultMapperFactory DEFAULT_MAPPER_FACTORY = clazz -> {
         String simpleClassName = clazz.getPackage().getName().concat(".Simple").concat(clazz.getSimpleName());
@@ -158,6 +159,15 @@ public class MapperEntityFactory implements EntityFactory {
         if (defaultClass != null) {
             return newInstance(defaultClass);
         }
+        if (Map.class == beanClass) {
+            return (T) new HashMap<>();
+        }
+        if (List.class == beanClass) {
+            return (T) new ArrayList<>();
+        }
+        if (Set.class == beanClass) {
+            return (T) new HashSet<>();
+        }
 
         throw new NotFoundException("can't create instance for " + beanClass);
     }
@@ -189,7 +199,7 @@ public class MapperEntityFactory implements EntityFactory {
     }
 
     public static class Mapper<T> {
-        Class<T> target;
+        Class<T>    target;
         Supplier<T> instanceGetter;
 
         public Mapper(Class<T> target, Supplier<T> instanceGetter) {

+ 1 - 1
hsweb-commons/hsweb-commons-model/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-api/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-oauth2/pom.xml

@@ -5,7 +5,9 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/pom.xml

@@ -23,7 +23,9 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 3 - 1
hsweb-commons/hsweb-commons-service/pom.xml

@@ -23,7 +23,9 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 1
hsweb-commons/hsweb-commons-utils/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 1
hsweb-commons/pom.xml

@@ -23,7 +23,8 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-async-job/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-cache/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent-counter</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-counter</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-counter/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-redis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-starter/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 22 - 1
hsweb-core/README.md

@@ -59,4 +59,25 @@ public class User  {
 
 注意: 1,2的功能由`hsweb-commons-dao`模块去实现,如果你不没有使用hsweb自带的dao实现,可能无法使用此功能.
 
-所有的字典都会注册到:`DictDefineRepository`,可通过此类去获取字典,以提供给前端或者其他地方使用.
+所有的字典都会注册到:`DictDefineRepository`,可通过此类去获取字典,以提供给前端或者其他地方使用.
+
+## ToString
+``org.hswebframework.web.bean.ToString``提供了对Bean转为String的功能.包括字段脱敏(打码).
+
+```java
+
+@lombok.Getter
+@lombok.Setter
+public class MyEntity{
+    
+    //敏感字段,在ToString的时候会给字段打码.比如: 185*****234
+    @org.hswebframework.web.bean.ToString.Ignore
+    private String userPhone;
+    
+    public String toString(){
+        return org.hswebframework.web.bean.ToString.toString(this);
+    }
+}
+
+```
+

+ 2 - 2
hsweb-core/pom.xml

@@ -5,7 +5,8 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -37,7 +38,6 @@
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
 </project>

+ 6 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/BeanFactory.java

@@ -0,0 +1,6 @@
+package org.hswebframework.web.bean;
+
+public interface BeanFactory {
+
+    <T> T newInstance(Class<T> beanType);
+}

+ 1 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/Copier.java

@@ -10,5 +10,6 @@ public interface Copier {
     default void copy(Object source, Object target, String... ignore){
         copy(source,target,new HashSet<>(Arrays.asList(ignore)),FastBeanCopier.DEFAULT_CONVERT);
     }
+
 }
 

+ 342 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/DefaultToStringOperator.java

@@ -0,0 +1,342 @@
+package org.hswebframework.web.bean;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.utils.time.DateFormatter;
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
+import static org.hswebframework.web.bean.ToString.Feature.disableNestProperty;
+import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@Slf4j
+public class DefaultToStringOperator<T> implements ToStringOperator<T> {
+
+    private PropertyDescriptor[] descriptors;
+
+    private Set<String> defaultIgnoreProperties;
+
+    private long defaultFeatures = ToString.DEFAULT_FEATURE;
+
+    private Map<String, PropertyDescriptor> descriptorMap;
+
+    private Map<String, BiFunction<Object, ConvertConfig, Object>> converts;
+
+    private Function<Object, String> coverStringConvert = (o) -> coverString(String.valueOf(o), 50);
+
+    private Function<Class, BiFunction<Object, ConvertConfig, Object>> simpleConvertBuilder = type -> {
+        if (Date.class.isAssignableFrom(type)) {
+            return (value, f) -> DateFormatter.toString(((Date) value), "yyyy-MM-dd HH:mm:ss");
+        } else {
+            return (value, f) -> value;
+        }
+    };
+
+    Predicate<Class> simpleTypePredicate = ((Predicate<Class>) String.class::isAssignableFrom)
+            .or(Class::isEnum)
+            .or(Class::isPrimitive)
+            .or(Date.class::isAssignableFrom)
+            .or(Number.class::isAssignableFrom)
+            .or(Boolean.class::isAssignableFrom);
+    private Class<T> targetType;
+
+    public DefaultToStringOperator(Class<T> targetType) {
+        this.targetType = targetType;
+        descriptors = BeanUtils.getPropertyDescriptors(targetType);
+        init();
+    }
+
+    public static String coverString(String str, double percent) {
+        if (str.length() == 1) {
+            return "*";
+        }
+
+        if (percent > 1) {
+            percent = percent / 100d;
+        }
+        percent = 1 - percent;
+        long size = Math.round(str.length() * percent);
+
+        long end = (str.length() - size / 2);
+
+        long start = str.length() - end;
+        start = start == 0 && percent > 0 ? 1 : start;
+        char[] chars = str.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            if (i >= start && i <= end - 1) {
+                chars[i] = '*';
+            }
+        }
+        return new String(chars);
+    }
+
+    @SuppressWarnings("all")
+    protected void init() {
+        converts = new HashMap<>();
+        descriptorMap = Arrays.stream(descriptors).collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
+        //获取类上的注解
+        ToString.Ignore classIgnore = AnnotationUtils.getAnnotation(targetType, ToString.Ignore.class);
+        ToString.Features features = AnnotationUtils.getAnnotation(targetType, ToString.Features.class);
+        if (null != features && features.value().length > 0) {
+            defaultFeatures = ToString.Feature.createFeatures(features.value());
+        } else {
+            defaultFeatures = ToString.DEFAULT_FEATURE;
+        }
+        defaultIgnoreProperties = classIgnore == null ?
+                new HashSet<>(Collections.emptySet())
+                : new HashSet<>(Arrays.asList(classIgnore.value()));
+
+        //是否打码
+        boolean defaultCover = classIgnore != null && classIgnore.cover();
+
+        for (PropertyDescriptor descriptor : descriptors) {
+            if ("class".equals(descriptor.getName())) {
+                continue;
+            }
+            Class propertyType = descriptor.getPropertyType();
+            String propertyName = descriptor.getName();
+            BiFunction<Object, ConvertConfig, Object> convert;
+            ToString.Ignore propertyIgnore = null;
+            long propertyFeature = 0;
+            try {
+                Field field = targetType.getDeclaredField(descriptor.getName());
+                propertyIgnore = field.getAnnotation(ToString.Ignore.class);
+                features = AnnotationUtils.getAnnotation(field, ToString.Features.class);
+                if (propertyIgnore != null) {
+                    for (String val : propertyIgnore.value()) {
+                        defaultIgnoreProperties.add(field.getName().concat(".").concat(val));
+                    }
+                }
+                if (null != features && features.value().length > 0) {
+                    propertyFeature = ToString.Feature.createFeatures(features.value());
+                }
+            } catch (NoSuchFieldException e) {
+                log.warn("无法获取字段{},该字段将不会被打码!", descriptor.getName());
+
+            }
+            //是否设置了打码
+            boolean cover = (propertyIgnore == null && defaultCover) || (propertyIgnore != null && propertyIgnore.cover());
+            //是否注解了ignore
+            boolean hide = propertyIgnore != null;
+
+            long finalPropertyFeature = propertyFeature;
+
+            if (simpleTypePredicate.test(propertyType)) {
+                BiFunction<Object, ConvertConfig, Object> simpleConvert = simpleConvertBuilder.apply(propertyType);
+                convert = (value, f) -> {
+                    long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
+
+                    value = simpleConvert.apply(value, f);
+                    if (hide || f.ignoreProperty.contains(propertyName)) {
+                        if (ToString.Feature.hasFeature(feature, ToString.Feature.coverIgnoreProperty)) {
+                            return coverStringConvert.apply(value);
+                        } else {
+                            return null;
+                        }
+                    }
+                    return value;
+                };
+
+            } else {
+                boolean toStringOverride = false;
+                try {
+                    toStringOverride = propertyType.getMethod("toString").getDeclaringClass() != Object.class;
+                } catch (NoSuchMethodException ignore) {
+                }
+                boolean finalToStringOverride = toStringOverride;
+                boolean justReturn = propertyType.isArray()
+                        || Collection.class.isAssignableFrom(propertyType)
+                        || Map.class.isAssignableFrom(propertyType);
+
+                convert = (value, f) -> {
+                    if (f.ignoreProperty.contains(propertyName)) {
+                        return null;
+                    }
+                    long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
+
+                    boolean jsonFormat = ToString.Feature.hasFeature(feature, ToString.Feature.jsonFormat);
+                    boolean propertyJsonFormat = ToString.Feature.hasFeature(finalPropertyFeature, ToString.Feature.jsonFormat);
+
+                    if (ToString.Feature.hasFeature(f.features, disableNestProperty)) {
+                        return null;
+                    }
+                    if (!jsonFormat && finalToStringOverride) {
+                        return String.valueOf(value);
+                    }
+
+                    Set<String> newIgnoreProperty = f.ignoreProperty
+                            .stream()
+                            .filter(property -> property.startsWith(propertyName.concat(".")))
+                            .map(property -> property.substring(propertyName.length() + 1))
+                            .collect(Collectors.toSet());
+
+                    if (justReturn) {
+                        if (value instanceof Object[]) {
+                            value = Arrays.asList(((Object[]) value));
+                        }
+                        if (value instanceof Map) {
+                            value = convertMap(((Map) value), feature, newIgnoreProperty);
+                        }
+                        if (value instanceof Collection) {
+                            value = ((Collection) value).stream()
+                                    .map((val) -> {
+                                        if (val instanceof Map) {
+                                            return convertMap(((Map) val), feature, newIgnoreProperty);
+                                        }
+                                        if (simpleTypePredicate.test(val.getClass())) {
+                                            return val;
+                                        }
+                                        ToStringOperator operator = ToString.getOperator(val.getClass());
+                                        if (operator instanceof DefaultToStringOperator) {
+                                            return ((DefaultToStringOperator) operator).toMap(val, feature, newIgnoreProperty);
+                                        }
+                                        return operator.toString(val, feature, newIgnoreProperty);
+                                    }).collect(Collectors.toList());
+
+                        }
+                        if (value instanceof Map) {
+                            value = convertMap(((Map) value), feature, newIgnoreProperty);
+                        }
+                        if (propertyJsonFormat) {
+                            return JSON.toJSONString(value);
+                        }
+                        return value;
+                    }
+
+                    ToStringOperator operator = ToString.getOperator(value.getClass());
+                    if (!propertyJsonFormat && operator instanceof DefaultToStringOperator) {
+                        return ((DefaultToStringOperator) operator).toMap(value, feature, newIgnoreProperty);
+                    } else {
+                        return operator.toString(value, feature, newIgnoreProperty);
+                    }
+                };
+            }
+            converts.put(descriptor.getName(), convert);
+        }
+    }
+
+    class ConvertConfig {
+        long        features;
+        Set<String> ignoreProperty;
+
+    }
+
+    protected Map<String, Object> convertMap(Map<String, Object> obj, long features, Set<String> ignoreProperty) {
+        if (ignoreProperty.isEmpty()) {
+            return obj;
+        }
+        boolean cover = ToString.Feature.hasFeature(features, coverIgnoreProperty);
+        boolean isNullPropertyToEmpty = ToString.Feature.hasFeature(features, nullPropertyToEmpty);
+        boolean isDisableNestProperty = ToString.Feature.hasFeature(features, disableNestProperty);
+
+        Map<String, Object> newMap = new HashMap<>(obj);
+        Set<String> ignore = new HashSet<>(ignoreProperty.size());
+        ignore.addAll(defaultIgnoreProperties);
+
+        for (Map.Entry<String, Object> entry : newMap.entrySet()) {
+            Object value = entry.getValue();
+
+            if (value == null) {
+                if (isNullPropertyToEmpty) {
+                    entry.setValue("");
+                }
+                continue;
+            }
+            Class type = value.getClass();
+            if (simpleTypePredicate.test(type)) {
+                value = simpleConvertBuilder.apply(type).apply(value, null);
+                if (ignoreProperty.contains(entry.getKey())) {
+                    if (cover) {
+                        value = coverStringConvert.apply(value);
+                    } else {
+                        ignore.add(entry.getKey());
+                    }
+                    entry.setValue(value);
+                }
+
+            } else {
+                if (isDisableNestProperty) {
+                    ignore.add(entry.getKey());
+                }
+            }
+        }
+        ignore.forEach(newMap::remove);
+        return newMap;
+    }
+
+    protected Map<String, Object> toMap(T target, long features, Set<String> ignoreProperty) {
+        Map<String, Object> map = target instanceof Map ? ((Map) target) : FastBeanCopier.copy(target, new LinkedHashMap<>());
+
+        Set<String> ignore = ignoreProperty == null || ignoreProperty.isEmpty() ? defaultIgnoreProperties : ignoreProperty;
+        ConvertConfig convertConfig = new ConvertConfig();
+        convertConfig.ignoreProperty = ignore;
+        convertConfig.features = features == -1 ? defaultFeatures : features;
+        Set<String> realIgnore = new HashSet<>();
+
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            Object value = entry.getValue();
+            if (value == null) {
+                if (ToString.Feature.hasFeature(features, ToString.Feature.nullPropertyToEmpty)) {
+                    boolean isSimpleType = false;
+                    PropertyDescriptor propertyDescriptor = descriptorMap.get(entry.getKey());
+                    Class propertyType = null;
+                    if (propertyDescriptor != null) {
+                        propertyType = propertyDescriptor.getPropertyType();
+                        isSimpleType = simpleTypePredicate.test(propertyType);
+                    }
+                    if (isSimpleType || propertyType == null) {
+                        entry.setValue("");
+                    } else if (propertyType.isArray() || Collection.class.isAssignableFrom(propertyType)) {
+                        entry.setValue(Collections.emptyList());
+                    } else {
+                        entry.setValue(Collections.emptyMap());
+                    }
+                }
+                continue;
+            }
+            BiFunction<Object, ConvertConfig, Object> converter = converts.get(entry.getKey());
+            if (null != converter) {
+                entry.setValue(converter.apply(value, convertConfig));
+            }
+            if (entry.getValue() == null) {
+                realIgnore.add(entry.getKey());
+            }
+        }
+        realIgnore.forEach(map::remove);
+
+        return map;
+    }
+
+    @Override
+    public String toString(T target, long features, Set<String> ignoreProperty) {
+        if (target == null) {
+            return "";
+        }
+        if (features == -1) {
+            features = defaultFeatures;
+        }
+
+        Map<String, Object> mapValue = toMap(target, features, ignoreProperty);
+        if (ToString.Feature.hasFeature(features, ToString.Feature.jsonFormat)) {
+            return JSON.toJSONString(mapValue);
+        }
+        StringJoiner joiner = new StringJoiner(", ", target.getClass().getSimpleName() + "(", ")");
+
+        mapValue.forEach((key, value) -> joiner.add(key.concat("=").concat(String.valueOf(value))));
+
+        return joiner.toString();
+    }
+}

+ 42 - 10
hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java

@@ -2,6 +2,7 @@ package org.hswebframework.web.bean;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.PropertyUtilsBean;
@@ -33,10 +34,27 @@ public final class FastBeanCopier {
 
     private static final Map<Class, Class> wrapperClassMapping = new HashMap<>();
 
-    public static final DefaultConvert DEFAULT_CONVERT = new DefaultConvert();
-
     public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
 
+    private static BeanFactory BEAN_FACTORY = new BeanFactory() {
+        @Override
+        @SneakyThrows
+        public <T> T newInstance(Class<T> beanType) {
+            return beanType == Map.class ? (T) new HashMap<>() : beanType.newInstance();
+        }
+    };
+
+    public static final DefaultConverter DEFAULT_CONVERT;
+
+    public static void setBeanFactory(BeanFactory beanFactory) {
+        BEAN_FACTORY = beanFactory;
+        DEFAULT_CONVERT.setBeanFactory(beanFactory);
+    }
+
+    public static BeanFactory getBeanFactory() {
+        return BEAN_FACTORY;
+    }
+
     static {
         wrapperClassMapping.put(byte.class, Byte.class);
         wrapperClassMapping.put(short.class, Short.class);
@@ -46,6 +64,15 @@ public final class FastBeanCopier {
         wrapperClassMapping.put(char.class, Character.class);
         wrapperClassMapping.put(boolean.class, Boolean.class);
         wrapperClassMapping.put(long.class, Long.class);
+        BEAN_FACTORY = new BeanFactory() {
+            @Override
+            @SneakyThrows
+            public <T> T newInstance(Class<T> beanType) {
+                return beanType == Map.class ? (T) new HashMap<>() : beanType.newInstance();
+            }
+        };
+        DEFAULT_CONVERT = new DefaultConverter();
+        DEFAULT_CONVERT.setBeanFactory(BEAN_FACTORY);
     }
 
     public static <T, S> T copy(S source, T target, String... ignore) {
@@ -265,7 +292,9 @@ public final class FastBeanCopier {
                 boolean hasGeneric = false;
                 if (field != null) {
                     String[] arr = Arrays.stream(ResolvableType.forField(field)
-                            .getGenerics()).map(ResolvableType::getRawClass)
+                            .getGenerics())
+                            .map(ResolvableType::getRawClass)
+                            .filter(Objects::nonNull)
                             .map(t -> t.getName().concat(".class"))
                             .toArray(String[]::new);
                     if (arr.length > 0) {
@@ -324,16 +353,16 @@ public final class FastBeanCopier {
                 } else {
                     if (Cloneable.class.isAssignableFrom(targetType)) {
                         try {
-                            convertCode.append("(" + getTypeName() + ")").append(getterCode).append(".clone()");
+                            convertCode.append("(").append(getTypeName()).append(")").append(getterCode).append(".clone()");
                         } catch (Exception e) {
                             convertCode.append(getterCode);
                         }
                     } else {
                         if ((Map.class.isAssignableFrom(targetType)
                                 || Collection.class.isAssignableFrom(type)) && hasGeneric) {
-                            convertCode.append("(" + getTypeName() + ")").append(convert);
+                            convertCode.append("(").append(getTypeName()).append(")").append(convert);
                         } else {
-                            convertCode.append("(" + getTypeName() + ")").append(getterCode);
+                            convertCode.append("(").append(getTypeName()).append(")").append(getterCode);
 //                            convertCode.append(getterCode);
                         }
 
@@ -398,7 +427,12 @@ public final class FastBeanCopier {
     }
 
 
-    static final class DefaultConvert implements Converter {
+    public static final class DefaultConverter implements Converter {
+        private BeanFactory beanFactory = BEAN_FACTORY;
+
+        public void setBeanFactory(BeanFactory beanFactory) {
+            this.beanFactory = beanFactory;
+        }
 
         public Collection newCollection(Class targetClass) {
 
@@ -504,9 +538,7 @@ public final class FastBeanCopier {
                     return converter.convert(targetClass, source);
                 }
 
-                T newTarget = targetClass == Map.class ? (T) new HashMap<>() : targetClass.newInstance();
-                copy(source, newTarget);
-                return newTarget;
+                return copy(source, beanFactory.newInstance(targetClass), this);
             } catch (Exception e) {
                 log.warn("复制类型{}->{}失败", source, targetClass, e);
                 throw new UnsupportedOperationException(e.getMessage(), e);

+ 134 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ToString.java

@@ -0,0 +1,134 @@
+package org.hswebframework.web.bean;
+
+import org.springframework.util.ClassUtils;
+
+import java.lang.annotation.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public class ToString {
+
+    public static long DEFAULT_FEATURE = Feature.createFeatures(
+            Feature.coverIgnoreProperty
+            , Feature.nullPropertyToEmpty
+//            , Feature.jsonFormat
+    );
+
+    public static final Map<Class, ToStringOperator> cache = new ConcurrentHashMap<>();
+
+    @SuppressWarnings("all")
+    public static <T> ToStringOperator<T> getOperator(Class<T> type) {
+        return cache.computeIfAbsent(type, DefaultToStringOperator::new);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> String toString(T target) {
+        return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> String toString(T target, String... ignoreProperty) {
+        return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target, ignoreProperty);
+    }
+
+    @Target({ElementType.TYPE, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @Documented
+    public @interface Ignore {
+
+        String[] value() default {};
+
+        boolean cover() default true;
+
+    }
+
+    @Target({ElementType.TYPE, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @Documented
+    public @interface Features {
+        Feature[] value() default {};
+    }
+
+    public enum Feature {
+
+        /**
+         * 什么也不配置
+         *
+         * @since 3.0.0-RC
+         */
+        empty,
+
+        /**
+         * 忽略为null的字段
+         *
+         * @since 3.0.0-RC
+         */
+        ignoreNullProperty,
+
+        /**
+         * null的字段转为空,如null字符串转为"", null的list转为[]
+         *
+         * @since 3.0.0-RC
+         */
+        nullPropertyToEmpty,
+
+        /**
+         * 排除的字段使用*进行遮盖,如: 张三 =? 张* , 18502314087 => 185****087
+         *
+         * @since 3.0.0-RC
+         */
+        coverIgnoreProperty,
+
+        /**
+         * 是否关闭嵌套属性toString
+         *
+         * @since 3.0.0-RC
+         */
+        disableNestProperty,
+
+        /**
+         * 以json方式进行格式化
+         *
+         * @since 3.0.0-RC
+         */
+        jsonFormat;
+
+
+        public long getMask() {
+            return 1L << ordinal();
+        }
+
+        public static boolean hasFeature(long features, Feature feature) {
+            long mast = feature.getMask();
+            return (features & mast) == mast;
+        }
+
+        public static long removeFeatures(long oldFeature, Feature... features) {
+            if (features == null) {
+                return 0L;
+            }
+            long value = oldFeature;
+            for (Feature feature : features) {
+                value &= ~feature.getMask();
+            }
+            return value;
+        }
+
+        public static long createFeatures(Feature... features) {
+            if (features == null) {
+                return 0L;
+            }
+            long value = 0L;
+            for (Feature feature : features) {
+                value |= feature.getMask();
+            }
+
+            return value;
+        }
+    }
+
+}

+ 17 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ToStringOperator.java

@@ -0,0 +1,17 @@
+package org.hswebframework.web.bean;
+
+
+import java.util.*;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public interface ToStringOperator<T> {
+
+    default String toString(T target, String... ignoreProperty) {
+        return toString(target, -1, ignoreProperty == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(ignoreProperty)));
+    }
+
+    String toString(T target, long features, Set<String> ignoreProperty);
+}

+ 121 - 12
hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java

@@ -1,19 +1,38 @@
 package org.hswebframework.web.dict;
 
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONType;
+import com.alibaba.fastjson.parser.DefaultJSONParser;
+import com.alibaba.fastjson.parser.JSONLexer;
+import com.alibaba.fastjson.parser.JSONToken;
+import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
+import com.alibaba.fastjson.serializer.JSONSerializable;
+import com.alibaba.fastjson.serializer.JSONSerializer;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.function.Predicate;
 
 /**
  * 枚举字典,使用枚举来实现数据字典,可通过集成此接口来实现一些有趣的功能.
  * ⚠️:如果使用了位运算来判断枚举,枚举数量不要超过64个,且顺序不要随意变动!
- * 如果枚举数量大于64,你应该使用{@link org.hswebframework.web.dict.apply.DictApply}来处理
+ * 如果枚举数量大于64,你应该使用{@link org.hswebframework.web.dict.apply.DictApply}来处理.
+ * ⚠️:如果要开启在反序列化json的时候,支持将对象反序列化枚举,由于fastJson目前的版本还不支持从父类获取注解,
+ * 所以需要在实现类上注解:<code>@JSONType(deserializer = EnumDict.EnumDictJSONDeserializer.class)</code>.
  *
  * @author zhouhao
  * @see 3.0
+ * @see EnumDictJSONDeserializer
+ * @see JSONSerializable
  */
-public interface EnumDict<V> {
+@JSONType(deserializer = EnumDict.EnumDictJSONDeserializer.class)
+public interface EnumDict<V> extends JSONSerializable {
+
     /**
      * 枚举选项的值,通常由字母或者数字组成,并且在同一个枚举中值唯一;对应数据库中的值通常也为此值
      *
@@ -41,7 +60,7 @@ public interface EnumDict<V> {
         return ordinal();
     }
 
-    default long getBit() {
+    default long getMask() {
         return 1L << index();
     }
 
@@ -55,13 +74,13 @@ public interface EnumDict<V> {
         return this == v
                 || getValue() == v
                 || getValue().equals(v)
-                || v.equals(getBit())
+                || v.equals(getMask())
                 || String.valueOf(getValue()).equalsIgnoreCase(String.valueOf(v))
                 || getText().equalsIgnoreCase(String.valueOf(v));
     }
 
     default boolean in(long bit) {
-        return (bit & getBit()) != 0;
+        return (bit & getMask()) != 0;
     }
 
     /**
@@ -120,30 +139,30 @@ public interface EnumDict<V> {
     }
 
     @SafeVarargs
-    static <T extends EnumDict> long toBit(T... t) {
+    static <T extends EnumDict> long toMask(T... t) {
         if (t == null) {
             return 0L;
         }
         long value = 0L;
         for (T t1 : t) {
-            value |= t1.getBit();
+            value |= t1.getMask();
         }
         return value;
     }
 
     @SafeVarargs
-    static <T extends EnumDict> boolean bitIn(long bit, T... t) {
-        long value = toBit(t);
+    static <T extends EnumDict> boolean maskIn(long bit, T... t) {
+        long value = toMask(t);
         return (bit & value) == value;
     }
 
     @SafeVarargs
-    static <T extends EnumDict> boolean bitInAny(long bit, T... t) {
-        long value = toBit(t);
+    static <T extends EnumDict> boolean maskInAny(long bit, T... t) {
+        long value = toMask(t);
         return (bit & value) != 0;
     }
 
-    static <T extends EnumDict> List<T> getByBit(Class<T> tClass, long bit) {
+    static <T extends EnumDict> List<T> getByMask(Class<T> tClass, long bit) {
         List<T> arr = new ArrayList<>();
         for (T t : tClass.getEnumConstants()) {
             if (t.in(bit)) {
@@ -152,4 +171,94 @@ public interface EnumDict<V> {
         }
         return arr;
     }
+
+    /**
+     * 默认在序列化为json时,默认会以对象方式写出枚举,可通过系统环境变量 <code>hsweb.enum.dict.disableWriteJSONObject</code>关闭默认设置。
+     * 比如: java -jar -Dhsweb.enum.dict.disableWriteJSONObject=true
+     */
+    boolean DEFAULT_WRITE_JSON_OBJECT = !Boolean.getBoolean("hsweb.enum.dict.disableWriteJSONObject");
+
+    /**
+     * @return 是否在序列化为json的时候, 将枚举以对象方式序列化
+     * @see this#DEFAULT_WRITE_JSON_OBJECT
+     */
+    default boolean isWriteJSONObjectEnabled() {
+        return DEFAULT_WRITE_JSON_OBJECT;
+    }
+
+    /**
+     * 当{@link this#isWriteJSONObjectEnabled()}返回true时,在序列化为json的时候,会写出此方法返回的对象
+     *
+     * @return 最终序列化的值
+     * @see this#isWriteJSONObjectEnabled()
+     */
+    default Object getWriteJSONObject() {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("value", getValue());
+        jsonObject.put("text", getText());
+        jsonObject.put("index", index());
+        jsonObject.put("mask", getMask());
+        return jsonObject;
+    }
+
+    @Override
+    default void write(JSONSerializer jsonSerializer, Object o, Type type, int i) throws IOException {
+        if (isWriteJSONObjectEnabled()) {
+            jsonSerializer.write(getWriteJSONObject());
+        } else {
+            jsonSerializer.write(getValue());
+        }
+    }
+
+    /**
+     * 自定义fastJson枚举序列化
+     */
+    class EnumDictJSONDeserializer implements ObjectDeserializer {
+
+        @Override
+        @SuppressWarnings("all")
+        public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
+            try {
+                Object value;
+                final JSONLexer lexer = parser.lexer;
+                final int token = lexer.token();
+                if (token == JSONToken.LITERAL_INT) {
+                    int intValue = lexer.intValue();
+                    lexer.nextToken(JSONToken.COMMA);
+
+                    return (T) EnumDict.find((Class) type, intValue);
+                } else if (token == JSONToken.LITERAL_STRING) {
+                    String name = lexer.stringVal();
+                    lexer.nextToken(JSONToken.COMMA);
+
+                    if (name.length() == 0) {
+                        return (T) null;
+                    }
+                    return (T) EnumDict.find((Class) type, name).orElse(null);
+                } else if (token == JSONToken.NULL) {
+                    lexer.nextToken(JSONToken.COMMA);
+                    return null;
+                } else {
+                    value = parser.parse();
+                    if (value instanceof Map) {
+                        return (T) EnumDict.find(((Class) type), ((Map) value).get("value"))
+                                .orElseGet(() ->
+                                        EnumDict.find(((Class) type), ((Map) value).get("text")).orElse(null));
+                    }
+                }
+
+                throw new JSONException("parse enum " + type + " error, value : " + value);
+            } catch (JSONException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new JSONException(e.getMessage(), e);
+            }
+        }
+
+        @Override
+        public int getFastMatchToken() {
+            return JSONToken.LITERAL_STRING;
+        }
+    }
+
 }

+ 4 - 3
hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java

@@ -26,6 +26,7 @@ public class FastBeanCopierTest {
         source.setNestObject2(Collections.singletonMap("name","mapTest"));
         NestObject nestObject = new NestObject();
         nestObject.setAge(10);
+        nestObject.setPassword("1234567");
         nestObject.setName("测试2");
         source.setNestObject(nestObject);
         source.setNestObject3(nestObject);
@@ -35,9 +36,9 @@ public class FastBeanCopierTest {
 
 
         long t = System.currentTimeMillis();
-        for (int i = 10_0000; i > 0; i--) {
-            FastBeanCopier.copy(source, target);
-        }
+//        for (int i = 10_0000; i > 0; i--) {
+//            FastBeanCopier.copy(source, target);
+//        }
         System.out.println(System.currentTimeMillis() - t);
 
         System.out.println(source);

+ 12 - 6
hsweb-core/src/test/java/org/hswebframework/web/bean/NestObject.java

@@ -1,15 +1,13 @@
 package org.hswebframework.web.bean;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 /**
  * @author zhouhao
  * @since
  */
-@Data
+@Getter
+@Setter
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
@@ -19,8 +17,16 @@ public class NestObject implements Cloneable {
     private int age;
 
 
+    @ToString.Ignore
+    private String password ;
+
     @Override
     public NestObject clone() throws CloneNotSupportedException {
-        return (NestObject)super.clone();
+        return (NestObject) super.clone();
+    }
+
+    @Override
+    public String toString() {
+        return ToString.toString(this);
     }
 }

+ 2 - 2
hsweb-core/src/test/java/org/hswebframework/web/bean/Source.java

@@ -31,11 +31,11 @@ public class Source {
 
     private NestObject nestObject;
 
-    private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1),new NestObject("test", 1));
+    private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1, "1234567"), new NestObject("test", 1, "1234567"));
 
     private Map<String, Object> nestObject2 = new HashMap<>();
 
-    private NestObject nestObject3;
+    private NestObject nestObject3 = new NestObject("test", 1, "1234567");
 
     private Color color = Color.RED;
 

+ 20 - 6
hsweb-core/src/test/java/org/hswebframework/web/bean/Target.java

@@ -1,6 +1,8 @@
 package org.hswebframework.web.bean;
 
 import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.ArrayList;
@@ -8,15 +10,20 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
-@Data
+import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
+import static org.hswebframework.web.bean.ToString.Feature.jsonFormat;
+import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
+
+@Getter
+@Setter
 public class Target {
-    private String name;
+    private String   name;
     private String[] ids;
 
 
     private Boolean boy;
     private boolean boy2;
-    private String boy3;
+    private String  boy3;
 
     private int age;
 
@@ -24,21 +31,24 @@ public class Target {
 
     private String age3;
 
-    private Date deleteTime=new Date();
+    private Date deleteTime = new Date();
 
     private String createTime;
 
     private Date updateTime;
 
+    @ToString.Features({coverIgnoreProperty,jsonFormat})
+    @ToString.Ignore(value = "password")
     private NestObject nestObject;
 
     private NestObject nestObject2;
 
-    private List<Map<String,Object>> nestObjects;
+    @ToString.Ignore(value = "password")
+    private List<Map<String, Object>> nestObjects;
 
+    @ToString.Ignore("password")
     private Map<String, Object> nestObject3;
 
-
     private int color;
 
     private Color color2;
@@ -55,4 +65,8 @@ public class Target {
     private Integer[] arr4;
 
 
+    @Override
+    public String toString() {
+        return ToString.toString(this);
+    }
 }

+ 19 - 7
hsweb-core/src/test/java/org/hswebframework/web/dict/DictDefineTest.java

@@ -1,5 +1,6 @@
 package org.hswebframework.web.dict;
 
+import com.alibaba.fastjson.JSON;
 import org.hswebframework.web.dict.defaults.DefaultClassDictDefine;
 import org.hswebframework.web.dict.defaults.DefaultDictDefineRepository;
 import org.hswebframework.web.dict.defaults.DefaultDictSupportApi;
@@ -22,6 +23,17 @@ public class DictDefineTest {
 
     private DictSupportApi api = new DefaultDictSupportApi(repository);
 
+    @Test
+    public void testJson(){
+
+        UserCode code=UserCode.CODE0;
+
+        String json  =JSON.toJSONString(code);
+        System.out.println(json);
+        Assert.assertNotNull(json);
+        Assert.assertNotNull(JSON.parseObject(json,UserCode.class));
+
+    }
     @Test
     public void testEnumDict() {
 
@@ -31,20 +43,20 @@ public class DictDefineTest {
 
         Assert.assertEquals(UserCode.SIMPLE, find(UserCode.class, UserCode.SIMPLE.getText()).orElse(null));
 
-        long bit = toBit( UserCode.values());
+        long bit = toMask( UserCode.values());
 
-        System.out.println(bitIn(bit,UserCode.SIMPLE,UserCode.TEST,UserCode.SIMPLE));
+        System.out.println(maskIn(bit,UserCode.SIMPLE,UserCode.TEST,UserCode.SIMPLE));
 
-        long bit2=toBit(UserCode.SIMPLE,UserCode.CODE0,UserCode.SIMPLE);
+        long bit2= toMask(UserCode.SIMPLE,UserCode.CODE0,UserCode.SIMPLE);
 
-        Assert.assertTrue(bitInAny(bit2,UserCode.SIMPLE,UserCode.CODE4,UserCode.CODE0));
-        Assert.assertFalse(bitInAny(bit2,UserCode.CODE1,UserCode.CODE4,UserCode.CODE5));
+        Assert.assertTrue(maskInAny(bit2,UserCode.SIMPLE,UserCode.CODE4,UserCode.CODE0));
+        Assert.assertFalse(maskInAny(bit2,UserCode.CODE1,UserCode.CODE4,UserCode.CODE5));
 
         for (UserCode userCode : UserCode.values()) {
             Assert.assertTrue(userCode.in(bit));
         }
 
-        List<UserCode> codes = getByBit(UserCode.class, bit);
+        List<UserCode> codes = getByMask(UserCode.class, bit);
 
 
     }
@@ -67,7 +79,7 @@ public class DictDefineTest {
         assertFalse(defines.isEmpty());
         assertEquals(defines.size(), 1);
 
-        assertEquals(defines.get(0).getItems().size(), 2);
+        assertEquals(defines.get(0).getItems().size(), UserCode.values());
 
 
     }

+ 6 - 0
hsweb-core/src/test/java/org/hswebframework/web/dict/UserCode.java

@@ -7,6 +7,7 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 @Dict(id = "UserCode")
+@JSONType(deserializer = EnumDict.EnumDictJSONDeserializer.class)
 public enum UserCode implements EnumDict {
 
     SIMPLE("SIMPLE", "TEXT", "测试"),
@@ -80,4 +81,9 @@ public enum UserCode implements EnumDict {
     private String text;
 
     private String comments;
+
+    @Override
+    public boolean isWriteJSONObjectEnabled() {
+        return true;
+    }
 }

+ 14 - 0
hsweb-datasource/README.md

@@ -5,6 +5,20 @@
 
 # example
 
+表达式方式:
+
+application.yml配置
+```xml
+hsweb:
+    datasource:
+        switcher:
+           test: # 只是一个标识
+              # 拦截类和方法的表达式
+              expression: org.hswebframework.**.*Service.find*
+              # 使用数据源
+              data-source-id: read_db
+```
+
 编程方式:
 ```java
   //切换到 id为mysql_read_01的数据源

+ 3 - 1
hsweb-datasource/hsweb-datasource-api/pom.xml

@@ -5,7 +5,9 @@
     <parent>
         <artifactId>hsweb-datasource</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+
+        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 62 - 31
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/AopDataSourceSwitcherAutoConfiguration.java

@@ -1,20 +1,26 @@
 package org.hswebframework.web.datasource;
 
 import org.aopalliance.intercept.MethodInterceptor;
-import org.hswebframework.web.AopUtils;
 import org.hswebframework.web.ExpressionUtils;
 import org.hswebframework.web.boost.aop.context.MethodInterceptorHolder;
-import org.hswebframework.web.datasource.DataSourceHolder;
-import org.hswebframework.web.datasource.annotation.UseDataSource;
-import org.hswebframework.web.datasource.annotation.UseDefaultDataSource;
 import org.hswebframework.web.datasource.exception.DataSourceNotFoundException;
+import org.hswebframework.web.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher;
+import org.hswebframework.web.datasource.strategy.DataSourceSwitchStrategyMatcher;
+import org.hswebframework.web.datasource.strategy.ExpressionDataSourceSwitchStrategyMatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.util.ClassUtils;
 
 import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.hswebframework.web.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.*;
 
 /**
  * 通过aop方式进行对注解方式切换数据源提供支持
@@ -26,40 +32,61 @@ import java.lang.reflect.Method;
 public class AopDataSourceSwitcherAutoConfiguration {
 
     @Bean
-    public SwitcherMethodMatcherPointcutAdvisor switcherMethodMatcherPointcutAdvisor() {
-        return new SwitcherMethodMatcherPointcutAdvisor();
+    @ConfigurationProperties(prefix = "hsweb.datasource")
+    public ExpressionDataSourceSwitchStrategyMatcher expressionDataSourceSwitchStrategyMatcher() {
+        return new ExpressionDataSourceSwitchStrategyMatcher();
+    }
+
+    @Bean
+    public AnnotationDataSourceSwitchStrategyMatcher annotationDataSourceSwitchStrategyMatcher() {
+        return new AnnotationDataSourceSwitchStrategyMatcher();
+    }
+
+    @Bean
+    public SwitcherMethodMatcherPointcutAdvisor switcherMethodMatcherPointcutAdvisor(List<DataSourceSwitchStrategyMatcher> matchers) {
+        return new SwitcherMethodMatcherPointcutAdvisor(matchers);
     }
 
     public static class SwitcherMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
-        private static final Logger logger = LoggerFactory.getLogger(SwitcherMethodMatcherPointcutAdvisor.class);
+        private static final Logger logger           = LoggerFactory.getLogger(SwitcherMethodMatcherPointcutAdvisor.class);
+        private static final long   serialVersionUID = 536295121851990398L;
+
+        private List<DataSourceSwitchStrategyMatcher> matchers;
 
-        public SwitcherMethodMatcherPointcutAdvisor() {
+        private Map<AnnotationDataSourceSwitchStrategyMatcher.CacheKey, DataSourceSwitchStrategyMatcher> cache = new ConcurrentHashMap<>();
+
+        public SwitcherMethodMatcherPointcutAdvisor(List<DataSourceSwitchStrategyMatcher> matchers) {
+            this.matchers = matchers;
             setAdvice((MethodInterceptor) methodInvocation -> {
-                logger.debug("switch datasource...");
-                UseDataSource useDataSource = AopUtils.findAnnotation(methodInvocation.getThis().getClass(),
-                        methodInvocation.getMethod(), UseDataSource.class);
-                if (useDataSource != null) {
-                    String id = useDataSource.value();
-                    if (id.contains("${")) {
-                        MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
-                        id = ExpressionUtils.analytical(id, holder.getArgs(), "spel");
-                    }
-                    if (!DataSourceHolder.existing(id)) {
-                        if (useDataSource.fallbackDefault()) {
+                CacheKey key = new CacheKey(ClassUtils.getUserClass(methodInvocation.getThis()), methodInvocation.getMethod());
+                DataSourceSwitchStrategyMatcher matcher = cache.get(key);
+                if (matcher == null) {
+                    logger.warn("method:{} not support switch datasource", methodInvocation.getMethod());
+                } else {
+                    MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
+                    Strategy strategy = matcher.getStrategy(holder.createParamContext());
+                    if (strategy == null) {
+                        logger.warn("strategy matcher found:{}, but strategy is null!", matcher);
+                    } else {
+                        logger.debug("switch datasource.use strategy:{}", strategy);
+                        if (strategy.isUseDefaultDataSource()) {
                             DataSourceHolder.switcher().useDefault();
                         } else {
-                            throw new DataSourceNotFoundException(id);
+                            String id = strategy.getDataSourceId();
+                            if (id.contains("${")) {
+                                id = ExpressionUtils.analytical(id, holder.getArgs(), "spel");
+                            }
+                            if (!DataSourceHolder.existing(id)) {
+                                if (strategy.isFallbackDefault()) {
+                                    DataSourceHolder.switcher().useDefault();
+                                } else {
+                                    throw new DataSourceNotFoundException(id);
+                                }
+                            } else {
+                                DataSourceHolder.switcher().use(id);
+                            }
                         }
-                    } else {
-                        DataSourceHolder.switcher().use(id);
-                    }
-                } else {
-                    UseDefaultDataSource useDefaultDataSource = AopUtils.findAnnotation(methodInvocation.getThis().getClass(),
-                            methodInvocation.getMethod(), UseDefaultDataSource.class);
-                    if (useDefaultDataSource == null) {
-                        logger.warn("can't found  annotation: UseDefaultDataSource !");
                     }
-                    DataSourceHolder.switcher().useDefault();
                 }
                 try {
                     return methodInvocation.proceed();
@@ -71,8 +98,12 @@ public class AopDataSourceSwitcherAutoConfiguration {
 
         @Override
         public boolean matches(Method method, Class<?> aClass) {
-            return AopUtils.findAnnotation(aClass, method, UseDataSource.class) != null ||
-                    AopUtils.findAnnotation(aClass, method, UseDefaultDataSource.class) != null;
+            CacheKey key = new CacheKey(aClass, method);
+            matchers.stream()
+                    .filter(matcher -> matcher.match(aClass, method))
+                    .findFirst()
+                    .ifPresent((matcher) -> cache.put(key, matcher));
+            return cache.containsKey(key);
         }
     }
 }

+ 1 - 1
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/annotation/UseDataSource.java

@@ -25,5 +25,5 @@ public @interface UseDataSource {
      * 将抛出 {@link org.hswebframework.web.datasource.exception.DataSourceNotFoundException}
      * @see DataSourceHolder#currentExisting()
      */
-    boolean fallbackDefault() default true;
+    boolean fallbackDefault() default false;
 }

+ 47 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/AnnotationDataSourceSwitchStrategyMatcher.java

@@ -0,0 +1,47 @@
+package org.hswebframework.web.datasource.strategy;
+
+import org.hswebframework.web.AopUtils;
+import org.hswebframework.web.datasource.annotation.UseDataSource;
+import org.hswebframework.web.datasource.annotation.UseDefaultDataSource;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public class AnnotationDataSourceSwitchStrategyMatcher extends CachedDataSourceSwitchStrategyMatcher {
+    @Override
+    public Strategy createStrategyIfMatch(Class target, Method method) {
+        UseDataSource useDataSource = AopUtils.findAnnotation(target, method, UseDataSource.class);
+        UseDefaultDataSource useDefaultDataSource = AopUtils.findAnnotation(target, method, UseDefaultDataSource.class);
+
+        boolean support = useDataSource != null || useDefaultDataSource != null;
+        if (support) {
+            return new Strategy() {
+                @Override
+                public boolean isUseDefaultDataSource() {
+                    return useDefaultDataSource != null;
+                }
+
+                @Override
+                public boolean isFallbackDefault() {
+                    return useDataSource != null && useDataSource.fallbackDefault();
+                }
+
+                @Override
+                public String getDataSourceId() {
+                    return useDataSource == null ? null : useDataSource.value();
+                }
+
+                @Override
+                public String toString() {
+                    return "Annotation Strategy(" + (useDataSource != null ? useDataSource : useDefaultDataSource) + ")";
+                }
+            };
+        }
+        return null;
+    }
+
+
+}

+ 66 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/CachedDataSourceSwitchStrategyMatcher.java

@@ -0,0 +1,66 @@
+package org.hswebframework.web.datasource.strategy;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.boost.aop.context.MethodInterceptorContext;
+import org.springframework.util.ClassUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@Slf4j
+public abstract class CachedDataSourceSwitchStrategyMatcher implements DataSourceSwitchStrategyMatcher {
+
+    static Map<CacheKey, Strategy> cache = new ConcurrentHashMap<>();
+
+    public abstract Strategy createStrategyIfMatch(Class target, Method method);
+
+    @Override
+    public boolean match(Class target, Method method) {
+        Strategy strategy = createStrategyIfMatch(target, method);
+        if (null != strategy) {
+            if (log.isDebugEnabled()) {
+                log.debug("create data source switcher strategy:{} for method:{}", strategy, method);
+            }
+            CacheKey cacheKey = new CacheKey(target, method);
+            cache.put(cacheKey, strategy);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Strategy getStrategy(MethodInterceptorContext context) {
+        Method method = context.getMethod();
+        Class target = ClassUtils.getUserClass(context.getTarget());
+        return cache.get(new CacheKey(target, method));
+    }
+
+    @AllArgsConstructor
+    public static class CacheKey {
+
+        private Class target;
+
+        private Method method;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof CacheKey)) {
+                return false;
+            }
+            CacheKey target = ((CacheKey) obj);
+            return target.target == this.target && target.method == method;
+        }
+
+        public int hashCode() {
+            int result = this.target != null ? this.target.hashCode() : 0;
+            result = 31 * result + (this.method != null ? this.method.hashCode() : 0);
+            return result;
+        }
+    }
+}

+ 60 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/DataSourceSwitchStrategyMatcher.java

@@ -0,0 +1,60 @@
+package org.hswebframework.web.datasource.strategy;
+
+import org.hswebframework.web.boost.aop.context.MethodInterceptorContext;
+import org.hswebframework.web.datasource.DynamicDataSource;
+import org.hswebframework.web.datasource.exception.DataSourceNotFoundException;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据源切换策略,可通过此接口来自定义数据源切换的方式
+ *
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public interface DataSourceSwitchStrategyMatcher {
+
+    /**
+     * 匹配类和方法,返回是否需要进行数据源切换
+     *
+     * @param target 类
+     * @param method 方法
+     * @return 是否需要进行数据源切换
+     */
+    boolean match(Class target, Method method);
+
+    /**
+     * 获取数据源切换策略
+     * @param context aop上下文
+     * @return 切换策略
+     */
+    Strategy getStrategy(MethodInterceptorContext context);
+
+    /**
+     * 数据源切换策略
+     */
+    interface Strategy {
+        /**
+         * 是否使用默认数据源,与 {@link this#getDataSourceId}互斥,只在{@link this#getDataSourceId}不为空时生效
+         *
+         * @return 是否使用默认数据源
+         */
+        boolean isUseDefaultDataSource();
+
+        /**
+         * 当数据源不存在时,是否回退为默认数据源,如果为false,当数据源不存在时,将会抛出异常{@link org.hswebframework.web.datasource.exception.DataSourceNotFoundException}
+         *
+         * @return 是否使用默认数据源
+         * @see DataSourceNotFoundException
+         */
+        boolean isFallbackDefault();
+
+        /**
+         * @return 要切换数据源的id
+         * @see DynamicDataSource#getId()
+         * @see org.hswebframework.web.datasource.switcher.DataSourceSwitcher#use(String)
+         */
+        String getDataSourceId();
+    }
+
+}

+ 67 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/ExpressionDataSourceSwitchStrategyMatcher.java

@@ -0,0 +1,67 @@
+package org.hswebframework.web.datasource.strategy;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.util.AntPathMatcher;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 表达式方式切换数据源,在配置文件中设置:
+ * <pre>
+ *     hsweb:
+ *        datasource:
+ *           switcher:
+ *              org.hswebframework.**.*Service.select*:
+ *                  data-source-id: test1
+ * </pre>
+ *
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public class ExpressionDataSourceSwitchStrategyMatcher extends CachedDataSourceSwitchStrategyMatcher {
+
+    @Getter
+    @Setter
+    private Map<String, ExpressionStrategy> switcher = new HashMap<>();
+
+    private static AntPathMatcher antPathMatcher = new AntPathMatcher(".");
+
+    @Override
+    public Strategy createStrategyIfMatch(Class target, Method method) {
+        if (switcher.isEmpty()) {
+            return null;
+        }
+        String text = target.getName().concat(".").concat(method.getName());
+
+        return switcher.entrySet().stream()
+                .filter(entry -> antPathMatcher.match(entry.getValue().getExpression(), text))
+                .peek(entry -> entry.getValue().setId(entry.getKey()))
+                .map(Map.Entry::getValue)
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Getter
+    @Setter
+    public static class ExpressionStrategy implements Strategy {
+        private boolean useDefaultDataSource = false;
+        private boolean fallbackDefault      = false;
+        private String  dataSourceId         = null;
+        private String expression;
+        private String id;
+
+        public boolean isUseDefaultDataSource() {
+            return useDefaultDataSource && dataSourceId == null;
+        }
+
+        @Override
+        public String toString() {
+            return "Expression Strategy(use(" + (isUseDefaultDataSource() ? "default" : getDataSourceId()) + "),expression:" + getExpression() + ")";
+        }
+    }
+}

+ 3 - 1
hsweb-datasource/hsweb-datasource-jta/pom.xml

@@ -5,7 +5,9 @@
     <parent>
         <artifactId>hsweb-datasource</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0-SNAPSHOT</version>
+        <version>3.0.0-RC-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 0 - 1
hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/AtomikosDataSourceConfig.java

@@ -3,7 +3,6 @@ package org.hswebframework.web.datasource.jta;
 import com.atomikos.jdbc.AtomikosDataSourceBean;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.datasource.config.DynamicDataSourceConfig;
 

+ 12 - 14
hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/InMemoryAtomikosDataSourceRepository.java

@@ -1,5 +1,7 @@
 package org.hswebframework.web.datasource.jta;
 
+import lombok.Getter;
+import lombok.Setter;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 import javax.annotation.PostConstruct;
@@ -11,46 +13,42 @@ import java.util.Map;
 /**
  * @author zhouhao
  */
-@ConfigurationProperties(prefix = "hsweb.dynamic")
+@ConfigurationProperties(prefix = "hsweb.datasource")
 public class InMemoryAtomikosDataSourceRepository implements JtaDataSourceRepository {
-    private Map<String, AtomikosDataSourceConfig> datasource = new HashMap<>();
 
-    public Map<String, AtomikosDataSourceConfig> getDatasource() {
-        return datasource;
-    }
+    @Getter
+    @Setter
+    private Map<String, AtomikosDataSourceConfig> jta = new HashMap<>();
 
-    public void setDatasource(Map<String, AtomikosDataSourceConfig> datasource) {
-        this.datasource = datasource;
-    }
 
     @PostConstruct
     public void init() {
-        datasource.forEach((id, config) -> {
+        jta.forEach((id, config) -> {
             if (config.getId() == null) {
                 config.setId(id);
             } else if (!config.getId().equals(id)) {
-                datasource.put(config.getId(), config);
+                jta.put(config.getId(), config);
             }
         });
     }
 
     @Override
     public List<AtomikosDataSourceConfig> findAll() {
-        return new ArrayList<>(datasource.values());
+        return new ArrayList<>(jta.values());
     }
 
     @Override
     public AtomikosDataSourceConfig findById(String dataSourceId) {
-        return datasource.get(dataSourceId);
+        return jta.get(dataSourceId);
     }
 
     @Override
     public AtomikosDataSourceConfig add(AtomikosDataSourceConfig config) {
-        return datasource.put(config.getId(), config);
+        return jta.put(config.getId(), config);
     }
 
     @Override
     public AtomikosDataSourceConfig remove(String dataSourceId) {
-        return datasource.remove(dataSourceId);
+        return jta.remove(dataSourceId);
     }
 }

+ 45 - 49
hsweb-datasource/hsweb-datasource-jta/src/main/java/org/hswebframework/web/datasource/jta/JtaDynamicDataSourceService.java

@@ -1,5 +1,6 @@
 package org.hswebframework.web.datasource.jta;
 
+import lombok.SneakyThrows;
 import org.hswebframework.web.datasource.DynamicDataSource;
 import org.hswebframework.web.datasource.DynamicDataSourceProxy;
 import org.hswebframework.web.datasource.config.DynamicDataSourceConfigRepository;
@@ -44,9 +45,8 @@ public class JtaDynamicDataSourceService extends AbstractDynamicDataSourceServic
     }
 
 
-
-
     @Override
+    @SneakyThrows
     protected DataSourceCache createCache(AtomikosDataSourceConfig config) {
         AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
         config.putProperties(atomikosDataSourceBean);
@@ -54,57 +54,53 @@ public class JtaDynamicDataSourceService extends AbstractDynamicDataSourceServic
         atomikosDataSourceBean.setUniqueResourceName("dynamic_ds_" + config.getId());
         AtomicInteger successCounter = new AtomicInteger();
         CountDownLatch downLatch = new CountDownLatch(1);
-        try {
-            DataSourceCache cache = new DataSourceCache(config.hashCode(), new DynamicDataSourceProxy(config.getId(), atomikosDataSourceBean), downLatch, config) {
-                @Override
-                public void closeDataSource() {
-                    super.closeDataSource();
-                    atomikosDataSourceBean.close();
-                    XADataSource dataSource = atomikosDataSourceBean.getXaDataSource();
-                    if (dataSource instanceof Closeable) {
-                        try {
-                            ((Closeable) dataSource).close();
-                        } catch (IOException e) {
-                            logger.error("close xa datasource error", e);
-                        }
-                    } else {
-                        logger.warn("XADataSource is not instanceof Closeable!", (Object) Thread.currentThread().getStackTrace());
+        DataSourceCache cache = new DataSourceCache(config.hashCode(), new DynamicDataSourceProxy(config.getId(), atomikosDataSourceBean), downLatch, config) {
+            @Override
+            public void closeDataSource() {
+                super.closeDataSource();
+                atomikosDataSourceBean.close();
+                XADataSource dataSource = atomikosDataSourceBean.getXaDataSource();
+                if (dataSource instanceof Closeable) {
+                    try {
+                        ((Closeable) dataSource).close();
+                    } catch (IOException e) {
+                        logger.error("close xa datasource error", e);
                     }
+                } else {
+                    logger.warn("XADataSource is not instanceof Closeable!", (Object) Thread.currentThread().getStackTrace());
                 }
-            };
-            //异步初始化
-            executor.execute(() -> {
-                try {
-                    atomikosDataSourceBean.init();
-                    successCounter.incrementAndGet();
-                    downLatch.countDown();
-                } catch (Exception e) {
-                    logger.error("init datasource {} error", config.getId(), e);
+            }
+        };
+        //异步初始化
+        executor.execute(() -> {
+            try {
+                atomikosDataSourceBean.init();
+                successCounter.incrementAndGet();
+                downLatch.countDown();
+            } catch (Exception e) {
+                logger.error("init datasource {} error", config.getId(), e);
 
-                    //atomikosDataSourceBean.close();
-                }
-            });
-            //初始化状态判断
-            executor.execute(() -> {
-                try {
-                    Thread.sleep(config.getInitTimeout() * 1000L);
-                } catch (InterruptedException ignored) {
-                    logger.warn(ignored.getMessage(), ignored);
-                    Thread.currentThread().interrupt();
-                } finally {
-                    if (successCounter.get() == 0) {
-                        // 初始化超时,认定为失败
-                        logger.error("init timeout ({}ms)", config.getInitTimeout());
-                        cache.closeDataSource();
-                        if (downLatch.getCount() > 0) {
-                            downLatch.countDown();
-                        }
+                //atomikosDataSourceBean.close();
+            }
+        });
+        //初始化状态判断
+        executor.execute(() -> {
+            try {
+                Thread.sleep(config.getInitTimeout() * 1000L);
+            } catch (InterruptedException ignored) {
+                logger.warn(ignored.getMessage(), ignored);
+                Thread.currentThread().interrupt();
+            } finally {
+                if (successCounter.get() == 0) {
+                    // 初始化超时,认定为失败
+                    logger.error("init timeout ({}ms)", config.getInitTimeout());
+                    cache.closeDataSource();
+                    if (downLatch.getCount() > 0) {
+                        downLatch.countDown();
                     }
                 }
-            });
-            return cache;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+            }
+        });
+        return cache;
     }
 }

+ 30 - 3
hsweb-datasource/hsweb-datasource-jta/src/test/java/org/hswebframework/web/datasource/jta/SimpleAtomikosTests.java

@@ -16,6 +16,8 @@ import org.hswebframework.ezorm.rdb.render.dialect.OracleRDBDatabaseMetaData;
 import org.hswebframework.ezorm.rdb.simple.SimpleDatabase;
 import org.hswebframework.web.datasource.DataSourceHolder;
 import org.hswebframework.web.datasource.DatabaseType;
+import org.hswebframework.web.datasource.annotation.UseDataSource;
+import org.hswebframework.web.datasource.exception.DataSourceNotFoundException;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,9 +65,9 @@ public class SimpleAtomikosTests extends AbstractTransactionalJUnit4SpringContex
         }
 
         public class DynDatabaseMeta extends RDBDatabaseMetaData {
-            private Map<DatabaseType, Dialect> dialectMap;
+            private Map<DatabaseType, Dialect>             dialectMap;
             private Map<DatabaseType, RDBDatabaseMetaData> metaDataMap;
-            private Map<DatabaseType, TableMetaParser> parserMap;
+            private Map<DatabaseType, TableMetaParser>     parserMap;
 
             public DynDatabaseMeta(SqlExecutor sqlExecutor) {
                 dialectMap = new HashMap<>();
@@ -150,6 +152,18 @@ public class SimpleAtomikosTests extends AbstractTransactionalJUnit4SpringContex
         Assert.assertNull(DataSourceHolder.switcher().currentDataSourceId());
         Assert.assertTrue(dynDsTest.testQuery().size() > 0);
 
+        dynDsTest.findAll();
+
+        dynDsTest.query();
+
+        dynDsTest.query();
+
+        try {
+            dynDsTest.query("test123");
+            Assert.assertTrue(false);
+        } catch (DataSourceNotFoundException e) {
+        }
+
         jmsTemplate.convertAndSend("test", "hello");
         Thread.sleep(1000);
     }
@@ -177,10 +191,23 @@ public class SimpleAtomikosTests extends AbstractTransactionalJUnit4SpringContex
                     .exec();
         }
 
-
         public List testQuery() throws SQLException {
             return database.getTable("s_user").createQuery().list();
         }
+
+        public List findAll() throws SQLException {
+            return database.getTable("s_user").createQuery().list();
+        }
+
+        @UseDataSource("test_ds")
+        public List query() throws SQLException {
+            return database.getTable("s_user").createQuery().list();
+        }
+
+        @UseDataSource("${#dataSourceId}")
+        public List query(String dataSourceId) throws SQLException {
+            return database.getTable("s_user").createQuery().list();
+        }
     }
 
 }

+ 8 - 2
hsweb-datasource/hsweb-datasource-jta/src/test/resources/application.yml

@@ -23,8 +23,8 @@ logging:
       com.atomikos: WARN
       org.hswebframework: DEBUG
 hsweb:
-  dynamic:
-    datasource:
+  datasource:
+    jta:
         test_ds:
             xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
             xa-properties:
@@ -43,6 +43,12 @@ hsweb:
           max-pool-size: 20
           borrow-connection-timeout: 1000
           init-timeout: 20
+    switcher:
+       test: # 只是一个标识
+          # 拦截表达式
+          expression: org.hswebframework.**.*DynDsTest.find*
+          # 使用数据源
+          data-source-id: test_ds
 
 config:
     test: 123

+ 0 - 0
hsweb-datasource/hsweb-datasource-web/pom.xml


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.