diff --git a/README.md b/README.md index 149c9aa5..74f5fe9a 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,18 @@ 本框架基于springboot为提供领域驱动设计与事件风暴开发落地,提供的范式开源框架。 +## Project Version | 项目版本说明 + +v.2.x 为springboot 2.x版本,使用jdk8版本 +v.3.x 为springboot 3.x版本,使用jdk17版本 + ## Project Modules Description | 项目模块介绍 * springboot-starter | Springboot领域驱动框架 * springboot-starter-data-fast | 快速数据呈现框架 -* springboot-starter-id-generator | Id自增策略框架 -* springboot-starter-security-jwt | security&jwt权限框架 +* springboot-starter-data-authorization | 数据权限框架 +* springboot-starter-flow | 流程引擎框架 +* springboot-starter-security | security权限框架支持基于JWT的无状态权限认证与Redis的有状态权限认证 ## SpringBoot DDD Architecture | SpringBoot DDD 框架图 @@ -37,17 +43,24 @@ ${last.version} - - + + + com.codingapi.springboot + springboot-starter-data-authorization + ${last.version} + + + + com.codingapi.springboot - springboot-starter-id-generator + springboot-starter-flow ${last.version} com.codingapi.springboot - springboot-starter-security-jwt + springboot-starter-security ${last.version} @@ -66,7 +79,7 @@ https://github.com/codingapi/springboot-framework/wiki ## Example -见 [springboot-example](https://github.com/codingapi/springboot-example) +见 [springboot-example](https://github.com/codingapi/springboot-example) ## Reference Documentation diff --git a/docs/wiki/home.md b/docs/wiki/home.md index df4ad067..4387f03d 100644 --- a/docs/wiki/home.md +++ b/docs/wiki/home.md @@ -16,14 +16,7 @@ maven install com.codingapi.springboot springboot-starter-data-fast ${last.version} - - - - - com.codingapi.springboot - springboot-starter-id-generator - ${last.version} - + @@ -36,6 +29,5 @@ maven install [springboot-starter](./springboot-starter) [springboot-starter-security-jwt](./springboot-starter-security-jwt) -[springboot-starter-id-generator](./springboot-starter-id-generator) [springboot-starter-data-fast](./springboot-starter-data-fast.md) \ No newline at end of file diff --git a/docs/wiki/springboot-starter-data-fast.md b/docs/wiki/springboot-starter-data-fast.md index 0466431d..aa35baca 100644 --- a/docs/wiki/springboot-starter-data-fast.md +++ b/docs/wiki/springboot-starter-data-fast.md @@ -2,46 +2,240 @@ springboot-starter-data-fast 基于JPA的快速API能力服务 +## FastRepository 的使用教程 + +继承FastRepository接口,实现自定义的接口,即可使用FastRepository的能力 ```java -package com.codingapi.springboot.example.query; -import com.codingapi.springboot.example.infrastructure.jpa.entity.DemoEntity; -import com.codingapi.springboot.example.infrastructure.jpa.pojo.PageSearch; -import com.codingapi.springboot.fast.annotation.FastController; -import com.codingapi.springboot.fast.annotation.FastMapping; -import com.codingapi.springboot.framework.dto.response.MultiResponse; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.RequestMethod; -@FastController -public interface FastDemoApi { +import com.codingapi.springboot.fast.entity.Demo; +import com.codingapi.springboot.fast.query.FastRepository; + +public interface DemoRepository extends FastRepository { + +} + + +``` +动态FastRepository的能力展示 + +``` + + // 重写findAll,通过Example查询 + @Test + void findAll() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.addFilter("name", "123"); + + Page page = demoRepository.findAll(request); + assertEquals(1, page.getTotalElements()); + } + + + // pageRequest 自定义条件查询 + @Test + void pageRequest1() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.addFilter("name", PageRequest.FilterRelation.LIKE, "%2%"); + //sql: select demo0_.id as id1_0_, demo0_.name as name2_0_, demo0_.sort as sort3_0_ from t_demo demo0_ where demo0_.name like ? limit ? + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } + + // pageRequest 自定义条件查询 + @Test + void pageRequest2() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.orFilters(Filter.as("name","123"),Filter.as("name","456")); + //sql: select demo0_.id as id1_0_, demo0_.name as name2_0_, demo0_.sort as sort3_0_ from t_demo demo0_ where demo0_.name=? or demo0_.name=? limit ? + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } + + + // 动态sql的List查询 + @Test + void dynamicListQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + List list = demoRepository.dynamicListQuery("from Demo where name = ?1", "123"); + assertEquals(1, list.size()); + } - @PreAuthorize(value = "hasRole('ROLE_ADMIN')") - @FastMapping( - method = RequestMethod.GET, - mapping = "/api/demo/findByName1", - value = "select d from DemoEntity d where name = :name", - countQuery = "select count(d) from DemoEntity d where name = :name") - MultiResponse findByName1(PageSearch query); + // 动态sql的分页查询 + @Test + void dynamicPageQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); - @PreAuthorize(value = "hasRole('ROLE_USER')") - @FastMapping( - method = RequestMethod.GET, - mapping = "/api/demo/findByName2", - value = "select d from DemoEntity d where name = :name", - countQuery = "select count(d) from DemoEntity d where name = :name") - MultiResponse findByName2(PageSearch query); + Page page = demoRepository.dynamicPageQuery("from Demo where name = ?1", PageRequest.of(1, 2), "123"); + assertEquals(1, page.getTotalElements()); + } + // 增加排序查询 + @Test + void sortQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + + request.addSort(Sort.by("id").descending()); + Page page = demoRepository.findAll(request); + assertEquals(page.getContent().get(0).getName(), "456"); + assertEquals(2, page.getTotalElements()); + } + +``` + +## ScriptMapping 教程 + +通过动态添加mvc mapping实现查询功能. + + +``` +ScriptMapping scriptMapping = new ScriptMapping({mapinggUrl}, {mapinggMethod}, {mappingGrovvry}); +scriptMappingRegister.addMapping(scriptMapping); + +``` +mapinggUrl 是mvc接口的地址 +mapinggMethod 是mvc接口的请求方式 +mappingGrovvry 是执行的查询脚本 + +脚本实例代码: +* 动态分页查询 +``` +// 获取name的请求参数 +var name = $request.getParameter("name",""); +var pageNumber = $request.getParameter("pageNumber",0); +var pageSize = $request.getParameter("pageSize",10); +// 创建分页对象 +var pageRequest = $request.pageRequest(pageNumber,pageSize); +// 动态组织sql +var sql = "select * from api_mapping where 1 =1 "; +var countSql = "select count(1) from api_mapping where 1 =1 "; +// 动态组织参数 +var params = []; +if(!"".equals(name)){ + sql += " and name = ? "; + countSql += " and name = ? "; + params.push(name); +} +sql += " limit ?,?"; +// 添加分页参数 +params.add(pageRequest.getOffset()); +params.add(pageRequest.getPageSize()); +// 执行分页查询 +return $jdbc.queryForPage(sql,countSql,pageRequest,params.toArray()); +``` +* 动态条件查询 +``` +// 获取name的请求参数 +var name = $request.getParameter("name",""); +// 动态组织sql +String sql = "select * from api_mapping where 1=1 "; +// 动态组织参数 +var params = []; +if(!"".equals(name)){ + sql += " and name = ? "; + params.add(name); } +// 执行查询 +return $jdbc.queryForList(sql,params.toArray()); +``` +脚本语法介绍: +* $request +``` +// 获取参数name的值,如果参数不存在,则返回默认值 +var name = $request.getParameter("name",""); +// 获取分页对象 +var pageRequest = $request.pageRequest(0,10); +// 获取分页对象的页码 +var pageNumber = pageRequest.getPageNumber(); +// 获取分页对象的每页记录数 +var pageSize = pageRequest.getPageSize(); +// 获取分页对象的偏移量 +var offset = pageRequest.getOffset(); +``` +* $jdbc +``` +// 查询jdbcSQL $jdbc.queryForList({sql},{params}) + +// 查询无条件的数据 +var res = $jdbc.queryForList("select * from api_mapping"); +// 查询有条件的数据 +var res = $jdbc.queryForList("select * from api_mapping where name = ?",name); +// 查询多条件的数据 +var res = $jdbc.queryForList("select * from api_mapping where name = ? and url = ?",name,url); + +// 分页查询 $jdbc.queryForPage({sql},{countSql},{pageRequest},{params}) +var res = $jdbc.queryForPage("select * from api_mapping where name = ? and url = ?", +"select count(1) from api_mapping where name = ? and url = ?",pageRequest,params.toArray()); +``` +* $jpa ``` -@FastController 用于标记当前接口为Fast接口 -@FastMapping 用于标记当前接口的映射关系 -mapping为接口映射路径,method为接口请求方法 -value为查询语句,countQuery为查询总数语句,query为查询参数,支持分页查询,排序查询,查询参数等等 -MultiResponse为返回结果 -@PreAuthorize(value = "hasRole('ROLE_USER')") 用于标记当前接口的权限,如果不需要权限可以不用添加 +// 查询jpa $jpa.listQuery({clazz},{sql},{params}) +// 查询无条件的数据 +var res = $jpa.listQuery(com.example.entity.NodeEntity.class,"from NodeEntity"); +// 查询有条件的数据 +var res = $jpa.listQuery(com.example.entity.NodeEntity.class,"from NodeEntity where name = ?",name); +``` \ No newline at end of file diff --git a/docs/wiki/springboot-starter-id-generator.md b/docs/wiki/springboot-starter-id-generator.md deleted file mode 100644 index 27bb96a9..00000000 --- a/docs/wiki/springboot-starter-id-generator.md +++ /dev/null @@ -1,34 +0,0 @@ -springboot-starter-id-generator - -一个单服务下的id自增构造器,为了解决id由数据库生成的问题,如果是分布式服务,可以使用snowflake算法生成id,如果是单服务,可以使用这个构造器生成id - -```java - -import com.codingapi.springboot.generator.IdGenerate; -import lombok.Getter; - -/** - * @author lorne - * @since 1.0.0 - */ -public class Demo implements IdGenerate { - - @Getter - private Integer id; - - @Getter - private String name; - - public Demo(String name) { - this.id = generateIntId(); - this.name = name; - } - -} - -``` - -只需要实现IdGenerate接口即可,然后调用generateIntId()方法即可生成id。 - -IdGenerate接口提供了两个方法,generateIntId()和generateLongId(),分别用于生成int类型和long类型的id。 -也可以使用generateStringId()方法生成String类型的id。 \ No newline at end of file diff --git a/docs/wiki/springboot-starter-security-jwt.md b/docs/wiki/springboot-starter-security.md similarity index 61% rename from docs/wiki/springboot-starter-security-jwt.md rename to docs/wiki/springboot-starter-security.md index dd984683..d33c5c4a 100644 --- a/docs/wiki/springboot-starter-security-jwt.md +++ b/docs/wiki/springboot-starter-security.md @@ -1,18 +1,28 @@ -springboot-starter-security-jwt 功能介绍 +springboot-starter-security 功能介绍 + +支持无状态的JWT和有状态的redis两种不同的token机制 配置文件,默认参数即说明 ```properties +# JWT开关 +codingapi.security.jwt.enable=true # JWT密钥 需大于32位的字符串 -codingapi.security.jwt-secret=codingapi.security.jwt.secretkey +codingapi.security.jwt.secret-key=codingapi.security.jwt.secretkey + +# JWT 有效时间(毫秒) 15分钟有效期 1000*60*15=900000 +codingapi.security.jwt.valid-time=900000 +# JWT 更换令牌时间(毫秒) 10分钟后更换令牌 1000*60*10=600000 +codingapi.security.jwt.rest-time=600000 + # JWT AES密钥 codingapi.security.ase-key=QUNEWCQlXiYqJCNYQ1phc0FDRFgkJV4mKiQjWENaYXM= # JWT AES IV -codingapi.security.aes-iv=QUNYRkdIQEVEUyNYQ1phcw== +codingapi.security.ase-iv=QUNYRkdIQEVEUyNYQ1phcw== -# JWT 有效时间(毫秒) 15分钟有效期 1000*60*15=900000 -codingapi.security.jwt-time=900000 -# JWT 更换令牌时间(毫秒) 10分钟后更换令牌 1000*60*10=600000 -codingapi.security.jwt-rest-time=600000 +# Redis开关 +#codingapi.security.redis.enable=true +#spring.data.redis.host=localhost +#spring.data.redis.port=6379 # Security 配置 请求权限拦截地址 codingapi.security.authenticated-urls=/api/** @@ -51,14 +61,22 @@ security默认的账户密码为admin/admin,可以通过重写UserDetailsServi 也可以通过数据库账户获取账户数据,请自己实现UserDetailsService接口 ## 登录拦截 -可以通过重写SecurityLoginHandler来实现自定义登录拦截 -```java +可以通过重写SecurityLoginHandler来实现自定义登录拦截,preHandle登录前的拦截处理,postHandle登录后的拦截处理 +``` @Bean - public SecurityLoginHandler securityLoginHandler(){ - return (request, response, handler) -> { - //TODO 自定义登录拦截 - }; - } + public SecurityLoginHandler securityLoginHandler() { + return new SecurityLoginHandler() { + @Override + public void preHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest handler) throws Exception { + + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest handler, Token token) { + + } + }; +} ``` ## 获取当前用户 @@ -66,15 +84,15 @@ security默认的账户密码为admin/admin,可以通过重写UserDetailsServi 通过TokenContext获取当前用户信息 ```java @GetMapping("/user") - public String user(){ - return TokenContext.current().getUsername(); - } +public String user(){ + return TokenContext.current().getUsername(); +} ``` 可以通过Token的extra字段来存储用户的更多信息,然后通过TokenContext获取 ```java @GetMapping("/user") - public String user(){ - return TokenContext.current().getExtra("user"); - } +public String user(){ + return TokenContext.current().getExtra("user"); +} ``` diff --git a/pom.xml b/pom.xml index 3ba9ed9b..999755a7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,43 +3,52 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 pom + + springboot-starter-data-authorization + org.springframework.boot spring-boot-starter-parent - 3.1.2 + 2.7.18 com.codingapi.springboot springboot-parent - 2.1.11 + 2.10.5 https://github.com/codingapi/springboot-framewrok springboot-parent springboot-parent project for Spring Boot - 17 + 8 yyyy-MM-dd HH:mm:ss - 17 - 17 + 8 + 8 3.0.1 3.11.0 - 2.10.3 - 1.6.3 - 1.6 + 3.6.1 + 1.6.13 + 3.1.0 ${project.version} - 2.0.28 - 0.11.5 - 2.11.0 - 1.7 + 2.0.53 + 0.12.6 + 2.17.0 + 3.17.0 + 1.8.1 + 1.12.0 0.10.2 0.9.16 - 1.70 + 1.79 1.2.0 - 2.0 + 2.2 + 4.0.24 + 2.3.232 + 5.6.2 + 5.0 @@ -72,10 +81,22 @@ + + com.esotericsoftware + kryo + ${esotericsoftware.kryo.version} + + + + com.h2database + h2 + ${h2.version} + + org.bouncycastle - bcprov-jdk15on - ${bcprov-jdk15on.version} + bcprov-jdk18on + ${bcprov-jdk18on.version} @@ -90,6 +111,18 @@ ${commons-crypto.version} + + commons-io + commons-io + ${commons-io.version} + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + org.perf4j perf4j @@ -134,7 +167,7 @@ com.codingapi.springboot - springboot-starter-security-jwt + springboot-starter-security ${codingapi.framework.version} @@ -156,6 +189,36 @@ ${commons-dbutils.version} + + org.apache.commons + commons-text + ${commons-text.version} + + + + org.apache.groovy + groovy + ${apache-groovy.version} + + + + org.apache.groovy + groovy-json + ${apache-groovy.version} + + + + org.apache.groovy + groovy-xml + ${apache-groovy.version} + + + + com.github.jsqlparser + jsqlparser + ${jsqlparser.version} + + @@ -216,9 +279,10 @@ springboot-starter - springboot-starter-security-jwt + springboot-starter-security + springboot-starter-data-authorization springboot-starter-data-fast - springboot-starter-id-generator + springboot-starter-flow @@ -228,9 +292,10 @@ springboot-starter - springboot-starter-security-jwt + springboot-starter-security + springboot-starter-data-authorization springboot-starter-data-fast - springboot-starter-id-generator + springboot-starter-flow @@ -238,7 +303,7 @@ org.jacoco jacoco-maven-plugin - 0.8.9 + 0.8.12 @@ -258,7 +323,7 @@ org.openclover clover-maven-plugin - 4.4.1 + 4.5.2 true true @@ -278,13 +343,13 @@ springboot-starter - springboot-starter-security-jwt + springboot-starter-security + springboot-starter-data-authorization springboot-starter-data-fast - springboot-starter-id-generator + springboot-starter-flow - diff --git a/springboot-starter-data-authorization/pom.xml b/springboot-starter-data-authorization/pom.xml new file mode 100644 index 00000000..fa3f09c0 --- /dev/null +++ b/springboot-starter-data-authorization/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + springboot-parent + com.codingapi.springboot + 2.10.5 + + + springboot-starter-data-authorization + springboot-starter-data-authorization project for Spring Boot + + + 8 + + + + + + com.github.jsqlparser + jsqlparser + + + + org.springframework.boot + spring-boot-starter-data-jpa + test + + + + org.springframework.boot + spring-boot-starter-data-jdbc + test + + + + com.h2database + h2 + test + + + + com.mysql + mysql-connector-j + test + + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + report + test + + report + + + + + + + + diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationConfiguration.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationConfiguration.java new file mode 100644 index 00000000..e36e604a --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationConfiguration.java @@ -0,0 +1,48 @@ +package com.codingapi.springboot.authorization; + + +import com.codingapi.springboot.authorization.filter.DataAuthorizationFilter; +import com.codingapi.springboot.authorization.handler.ColumnHandler; +import com.codingapi.springboot.authorization.handler.RowHandler; +import com.codingapi.springboot.authorization.interceptor.SQLInterceptor; +import com.codingapi.springboot.authorization.properties.DataAuthorizationProperties; +import com.codingapi.springboot.authorization.register.ConditionHandlerRegister; +import com.codingapi.springboot.authorization.register.DataAuthorizationContextRegister; +import com.codingapi.springboot.authorization.register.ResultSetHandlerRegister; +import com.codingapi.springboot.authorization.register.SQLInterceptorRegister; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class DataAuthorizationConfiguration { + + @Bean + @ConfigurationProperties(prefix = "codingapi.data-authorization") + public DataAuthorizationProperties dataAuthorizationProperties(){ + return new DataAuthorizationProperties(); + } + + @Bean + public ConditionHandlerRegister conditionHandlerRegister(@Autowired(required = false) RowHandler rowHandler) { + return new ConditionHandlerRegister(rowHandler); + } + + @Bean + public ResultSetHandlerRegister resultSetHandlerRegister(@Autowired(required = false) ColumnHandler columnHandler) { + return new ResultSetHandlerRegister(columnHandler); + } + + @Bean + public SQLInterceptorRegister sqlInterceptorRegister(@Autowired(required = false) SQLInterceptor sqlInterceptor) { + return new SQLInterceptorRegister(sqlInterceptor); + } + + @Bean + public DataAuthorizationContextRegister dataAuthorizationContextRegister(@Autowired(required = false) List dataAuthorizationFilters) { + return new DataAuthorizationContextRegister(dataAuthorizationFilters); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationContext.java new file mode 100644 index 00000000..9af58d8a --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/DataAuthorizationContext.java @@ -0,0 +1,81 @@ +package com.codingapi.springboot.authorization; + +import com.codingapi.springboot.authorization.filter.DataAuthorizationFilter; +import com.codingapi.springboot.authorization.handler.Condition; +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 数据权限上下文 + */ +public class DataAuthorizationContext { + + @Getter + private final static DataAuthorizationContext instance = new DataAuthorizationContext(); + + private final List filters; + + private DataAuthorizationContext() { + this.filters = new ArrayList<>(); + } + + /** + * 添加数据权限过滤器 + * @param filter 数据权限过滤器 + */ + public void addDataAuthorizationFilter(DataAuthorizationFilter filter) { + this.filters.add(filter); + } + + /** + * 清空数据权限过滤器 + */ + public void clearDataAuthorizationFilters() { + this.filters.clear(); + } + + /** + * 列权限 + * @param interceptState 拦截状态 + * @param tableName 表名(或别名) + * @param columnName 列名 (或别名) + * @param value 值 + * @return T + * @param 泛型 + */ + public T columnAuthorization(SQLInterceptState interceptState, String tableName, String columnName, T value) { + if (interceptState != null && interceptState.hasIntercept()) { + String realTableName = interceptState.getTableName(tableName); + String realColumnName = interceptState.getColumnName(tableName,columnName); + + for (DataAuthorizationFilter filter : filters) { + if (filter.supportColumnAuthorization(realTableName, realColumnName, value)) { + return filter.columnAuthorization(realTableName, realColumnName, value); + } + } + } + return value; + } + + /** + * 行权限 + * @param tableName 表名 + * @param tableAlias 别名 + * @return Condition 增加的过滤条件 + */ + public Condition rowAuthorization(String tableName, String tableAlias) { + if (StringUtils.hasText(tableName) && StringUtils.hasText(tableAlias)) { + for (DataAuthorizationFilter filter : filters) { + if (filter.supportRowAuthorization(tableName, tableAlias)) { + return filter.rowAuthorization(tableName, tableAlias); + } + } + } + return null; + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/enhancer/DataPermissionSQLEnhancer.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/enhancer/DataPermissionSQLEnhancer.java new file mode 100644 index 00000000..3c39d992 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/enhancer/DataPermissionSQLEnhancer.java @@ -0,0 +1,122 @@ +package com.codingapi.springboot.authorization.enhancer; + + +import com.codingapi.springboot.authorization.handler.Condition; +import com.codingapi.springboot.authorization.handler.RowHandler; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.*; + +import java.sql.SQLException; +import java.util.List; + +/** + * 数据权限 SQL 增强器 + */ +public class DataPermissionSQLEnhancer { + + private final String sql; + private final RowHandler rowHandler; + private final TableColumnAliasHolder tableColumnAliasHolder; + private final Statement statement; + + + // 构造函数 + public DataPermissionSQLEnhancer(String sql, RowHandler rowHandler) throws SQLException { + try { + // 如何sql中存在? 则在?后面添加空格 + this.sql = sql.replaceAll("\\?", " ? "); + this.rowHandler = rowHandler; + this.statement = CCJSqlParserUtil.parse(this.sql); + this.tableColumnAliasHolder = new TableColumnAliasHolder(statement); + } catch (Exception e) { + throw new SQLException(e); + } + } + + // 获取增强后的SQL + public String getNewSQL() throws SQLException { + try { + if (statement instanceof Select) { + tableColumnAliasHolder.holderAlias(); + Select select = (Select) statement; + this.deepMatch(select); + return statement.toString(); + } + } catch (Exception e) { + throw new SQLException(e); + } + return sql; + } + + + private void deepMatch(Select select) throws Exception { + if (select instanceof PlainSelect) { + PlainSelect plainSelect = select.getPlainSelect(); + this.enhanceDataPermissionInSelect(plainSelect); + } + if (select instanceof SetOperationList) { + SetOperationList setOperationList = select.getSetOperationList(); + List selectList = setOperationList.getSelects(); + for (Select selectItem : selectList) { + this.deepSearch(selectItem); + } + } + } + + + // 增强 SELECT 语句 + private void searchSubSelect(String parent, PlainSelect plainSelect) { + FromItem fromItem = plainSelect.getFromItem(); + + // FROM 项是表 + if (fromItem instanceof Table) { + this.appendTableAlias(fromItem); + Table table = (Table) fromItem; + this.appendColumnAlias(parent, table.getName(), plainSelect.getSelectItems()); + } + + + // FROM是子查询 + if (fromItem instanceof Select) { + PlainSelect subPlainSelect = ((Select) fromItem).getPlainSelect(); + this.appendColumnAlias(parent, null, plainSelect.getSelectItems()); + parent = fromItem.getAlias().getName(); + this.searchSubSelect(parent, subPlainSelect); + } + + // 处理JOIN或关联子查询 + if (plainSelect.getJoins() != null) { + for (Join join : plainSelect.getJoins()) { + if (join.getRightItem() instanceof Select) { + FromItem currentItem = join.getRightItem(); + PlainSelect subPlainSelect = ((Select) currentItem).getPlainSelect(); + this.appendColumnAlias(parent, null, plainSelect.getSelectItems()); + parent = currentItem.getAlias().getName(); + this.searchSubSelect(parent, subPlainSelect); + } + if (join.getRightItem() instanceof Table) { + FromItem currentItem = join.getRightItem(); + this.appendTableAlias(currentItem); + Table table = (Table) currentItem; + this.appendColumnAlias(parent, table.getName(), plainSelect.getSelectItems()); + } + } + } + } + + + /** + * 添加表别名 + * + * @param fromItem 表 + */ + private void appendTableAlias(FromItem fromItem) { + Table table = (Table) fromItem; + Alias alias = table.getAlias(); + String aliasName = alias != null ? alias.getName() : table.getName(); + aliasContext.addTable(aliasName, table.getName()); + } + + + /** + * 添加列别名 + * + * @param parent 父表别名 + * @param selectItems 列 + */ + private void appendColumnAlias(String parent, String tableName, List> selectItems) { + if (selectItems != null) { + for (SelectItem selectItem : selectItems) { + if (selectItem.getExpression() instanceof Column) { + Column column = (Column) selectItem.getExpression(); + if (column.getTable() != null) { + tableName = column.getTable().getName(); + } + String columnName = column.getColumnName(); + Alias columnAlias = selectItem.getAlias(); + String aliasName = columnAlias != null ? selectItem.getAlias().getName() : columnName; + aliasContext.addColumn(parent, tableName, columnName, aliasName); + } + } + } + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/exception/NotAuthorizationException.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/exception/NotAuthorizationException.java new file mode 100644 index 00000000..29a7f385 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/exception/NotAuthorizationException.java @@ -0,0 +1,13 @@ +package com.codingapi.springboot.authorization.exception; + +import java.sql.SQLException; + +public class NotAuthorizationException extends SQLException { + + public NotAuthorizationException() { + } + + public NotAuthorizationException(String reason) { + super(reason); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DataAuthorizationFilter.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DataAuthorizationFilter.java new file mode 100644 index 00000000..69789928 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DataAuthorizationFilter.java @@ -0,0 +1,44 @@ +package com.codingapi.springboot.authorization.filter; + +import com.codingapi.springboot.authorization.handler.Condition; + +/** + * 数据权限过滤器 + */ +public interface DataAuthorizationFilter { + + /** + * 列权限过滤 + * @param tableName 表名 + * @param columnName 列名 + * @param value 值 + * @return 过滤后的值 + * @param T + */ + T columnAuthorization(String tableName, String columnName,T value); + + /** + * 行权限过滤 + * @param tableName 表名 + * @param tableAlias 表别名 + * @return 过滤后拦截sql条件 + */ + Condition rowAuthorization(String tableName, String tableAlias); + + /** + * 是否支持列权限过滤 + * @param tableName 表名 + * @param columnName 列名 + * @param value 值 + * @return 是否支持 + */ + boolean supportColumnAuthorization(String tableName, String columnName, Object value); + + /** + * 是否支持行权限过滤 + * @param tableName 表名 + * @param tableAlias 表别名 + * @return 是否支持 + */ + boolean supportRowAuthorization(String tableName, String tableAlias); +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DefaultDataAuthorizationFilter.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DefaultDataAuthorizationFilter.java new file mode 100644 index 00000000..ecc26881 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/filter/DefaultDataAuthorizationFilter.java @@ -0,0 +1,27 @@ +package com.codingapi.springboot.authorization.filter; + +import com.codingapi.springboot.authorization.handler.Condition; + +public class DefaultDataAuthorizationFilter implements DataAuthorizationFilter{ + + @Override + public T columnAuthorization(String tableName, String columnName, T value) { + return value; + } + + @Override + public Condition rowAuthorization(String tableName, String tableAlias) { + return null; + } + + @Override + public boolean supportColumnAuthorization(String tableName, String columnName, Object value) { + return false; + } + + @Override + public boolean supportRowAuthorization(String tableName, String tableAlias) { + return false; + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandler.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandler.java new file mode 100644 index 00000000..0f1da82a --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandler.java @@ -0,0 +1,74 @@ +package com.codingapi.springboot.authorization.handler; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; + +/** + * 列表拦截器 + */ +public interface ColumnHandler { + + String getString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value); + + boolean getBoolean(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, boolean value); + + byte getByte(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte value); + + short getShort(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, short value); + + int getInt(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, int value); + + long getLong(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, long value); + + float getFloat(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, float value); + + double getDouble(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, double value); + + BigDecimal getBigDecimal(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, BigDecimal value); + + byte[] getBytes(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte[] value); + + Date getDate(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Date value); + + Time getTime(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Time value); + + Timestamp getTimestamp(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Timestamp value); + + InputStream getAsciiStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value); + + InputStream getUnicodeStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value); + + InputStream getBinaryStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value); + + Object getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Object value); + + Reader getCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value); + + Ref getRef(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Ref value); + + Blob getBlob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Blob value); + + Clob getClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Clob value); + + Array getArray(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Array value); + + URL getURL(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, URL value); + + NClob getNClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, NClob value); + + SQLXML getSQLXML(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, SQLXML value); + + String getNString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value); + + Reader getNCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value); + + RowId getRowId(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, RowId value); + + T getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, T value, Class type); + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandlerContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandlerContext.java new file mode 100644 index 00000000..fc0ef6d3 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/ColumnHandlerContext.java @@ -0,0 +1,144 @@ +package com.codingapi.springboot.authorization.handler; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import lombok.Getter; +import lombok.Setter; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; + +/** + * ResultSet 拦截器 + */ +@Setter +public class ColumnHandlerContext { + + @Getter + private final static ColumnHandlerContext instance = new ColumnHandlerContext(); + + private ColumnHandlerContext() { + this.columnHandler = new DefaultColumnHandler(); + } + + private ColumnHandler columnHandler; + + + public String getString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value) { + return columnHandler.getString(interceptState, columnIndex, tableName, columnName, value); + } + + public short getShort(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, short value) { + return columnHandler.getShort(interceptState, columnIndex, tableName, columnName, value); + } + + public boolean getBoolean(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, boolean value) { + return columnHandler.getBoolean(interceptState, columnIndex, tableName, columnName, value); + } + + public byte getByte(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte value) { + return columnHandler.getByte(interceptState, columnIndex, tableName, columnName, value); + } + + public int getInt(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, int value) { + return columnHandler.getInt(interceptState, columnIndex, tableName, columnName, value); + } + + public long getLong(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, long value) { + return columnHandler.getLong(interceptState, columnIndex, tableName, columnName, value); + } + + public float getFloat(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, float value) { + return columnHandler.getFloat(interceptState, columnIndex, tableName, columnName, value); + } + + public double getDouble(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, double value) { + return columnHandler.getDouble(interceptState, columnIndex, tableName, columnName, value); + } + + public BigDecimal getBigDecimal(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, BigDecimal value) { + return columnHandler.getBigDecimal(interceptState, columnIndex, tableName, columnName, value); + } + + public byte[] getBytes(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte[] value) { + return columnHandler.getBytes(interceptState, columnIndex, tableName, columnName, value); + } + + public Timestamp getTimestamp(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Timestamp value) { + return columnHandler.getTimestamp(interceptState, columnIndex, tableName, columnName, value); + } + + public Time getTime(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Time value) { + return columnHandler.getTime(interceptState, columnIndex, tableName, columnName, value); + } + + public Date getDate(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Date value) { + return columnHandler.getDate(interceptState, columnIndex, tableName, columnName, value); + } + + public InputStream getAsciiStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return columnHandler.getAsciiStream(interceptState, columnIndex, tableName, columnName, value); + } + + public InputStream getUnicodeStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return columnHandler.getUnicodeStream(interceptState, columnIndex, tableName, columnName, value); + } + + public InputStream getBinaryStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return columnHandler.getBinaryStream(interceptState, columnIndex, tableName, columnName, value); + } + + public Object getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Object value) { + return columnHandler.getObject(interceptState, columnIndex, tableName, columnName, value); + } + + public Reader getCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value) { + return columnHandler.getCharacterStream(interceptState, columnIndex, tableName, columnName, value); + } + + public Ref getRef(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Ref value) { + return columnHandler.getRef(interceptState, columnIndex, tableName, columnName, value); + } + + public Blob getBlob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Blob value) { + return columnHandler.getBlob(interceptState, columnIndex, tableName, columnName, value); + } + + public Clob getClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Clob value) { + return columnHandler.getClob(interceptState, columnIndex, tableName, columnName, value); + } + + public Array getArray(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Array value) { + return columnHandler.getArray(interceptState, columnIndex, tableName, columnName, value); + } + + public URL getURL(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, URL value) { + return columnHandler.getURL(interceptState, columnIndex, tableName, columnName, value); + } + + public NClob getNClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, NClob value) { + return columnHandler.getNClob(interceptState, columnIndex, tableName, columnName, value); + } + + public SQLXML getSQLXML(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, SQLXML value) { + return columnHandler.getSQLXML(interceptState, columnIndex, tableName, columnName, value); + } + + public String getNString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value) { + return columnHandler.getNString(interceptState, columnIndex, tableName, columnName, value); + } + + public Reader getNCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value) { + return columnHandler.getNCharacterStream(interceptState, columnIndex, tableName, columnName, value); + } + + public RowId getRowId(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, RowId value) { + return columnHandler.getRowId(interceptState, columnIndex, tableName, columnName, value); + } + + public T getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, T value, Class type) { + return columnHandler.getObject(interceptState, columnIndex, tableName, columnName, value, type); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/Condition.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/Condition.java new file mode 100644 index 00000000..3cb6d62e --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/Condition.java @@ -0,0 +1,33 @@ +package com.codingapi.springboot.authorization.handler; + +import lombok.Getter; + +/** + * 查询条件 + */ +@Getter +public class Condition { + + private final String condition; + + private Condition(String condition) { + this.condition = condition; + } + + public static Condition customCondition(String condition) { + return new Condition(condition); + } + + public static Condition formatCondition(String condition, Object... args) { + return new Condition(String.format(condition, args)); + } + + public static Condition emptyCondition() { + return null; + } + + public static Condition defaultCondition() { + return new Condition("1=1"); + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultColumnHandler.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultColumnHandler.java new file mode 100644 index 00000000..ff60b751 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultColumnHandler.java @@ -0,0 +1,161 @@ +package com.codingapi.springboot.authorization.handler; + +import com.codingapi.springboot.authorization.DataAuthorizationContext; +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; + +/** + * 默认ResultSet 拦截器 + */ +public class DefaultColumnHandler implements ColumnHandler { + + @Override + public String getString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public boolean getBoolean(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, boolean value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public byte getByte(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public short getShort(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, short value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public int getInt(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, int value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public long getLong(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, long value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public float getFloat(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, float value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public double getDouble(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, double value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public BigDecimal getBigDecimal(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, BigDecimal value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public byte[] getBytes(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, byte[] value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Date getDate(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Date value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Time getTime(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Time value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Timestamp getTimestamp(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Timestamp value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public InputStream getAsciiStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public InputStream getUnicodeStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public InputStream getBinaryStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, InputStream value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Object getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Object value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Reader getCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Ref getRef(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Ref value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Blob getBlob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Blob value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Clob getClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Clob value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Array getArray(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Array value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public URL getURL(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, URL value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public NClob getNClob(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, NClob value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public SQLXML getSQLXML(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, SQLXML value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public String getNString(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, String value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public Reader getNCharacterStream(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, Reader value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public RowId getRowId(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, RowId value) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } + + @Override + public T getObject(SQLInterceptState interceptState, int columnIndex, String tableName, String columnName, T value, Class type) { + return DataAuthorizationContext.getInstance().columnAuthorization(interceptState,tableName, columnName, value); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultRowHandler.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultRowHandler.java new file mode 100644 index 00000000..972075cf --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/DefaultRowHandler.java @@ -0,0 +1,11 @@ +package com.codingapi.springboot.authorization.handler; + +import com.codingapi.springboot.authorization.DataAuthorizationContext; + +public class DefaultRowHandler implements RowHandler { + + @Override + public Condition handler(String subSql, String tableName, String tableAlias) { + return DataAuthorizationContext.getInstance().rowAuthorization(tableName, tableAlias); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandler.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandler.java new file mode 100644 index 00000000..7262d5c4 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandler.java @@ -0,0 +1,19 @@ +package com.codingapi.springboot.authorization.handler; + +/** + * 行数据权限处理器 + */ +public interface RowHandler { + + /** + * 查询条件拦截 + * + * @param subSql 查询子SQL语句 + * @param tableName 表名 + * @param tableAlias 表别名 + * @return 条件语句 + */ + Condition handler(String subSql, String tableName, String tableAlias); + + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandlerContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandlerContext.java new file mode 100644 index 00000000..5d096177 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/handler/RowHandlerContext.java @@ -0,0 +1,18 @@ +package com.codingapi.springboot.authorization.handler; + +import lombok.Getter; +import lombok.Setter; + +@Setter +public class RowHandlerContext { + + @Getter + private final static RowHandlerContext instance = new RowHandlerContext(); + + @Getter + private RowHandler rowHandler; + + private RowHandlerContext() { + this.rowHandler = new DefaultRowHandler(); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DataPermissionSQL.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DataPermissionSQL.java new file mode 100644 index 00000000..b0b47df8 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DataPermissionSQL.java @@ -0,0 +1,18 @@ +package com.codingapi.springboot.authorization.interceptor; + +import com.codingapi.springboot.authorization.enhancer.TableColumnAliasContext; +import lombok.Getter; + +@Getter +public class DataPermissionSQL { + + private final String sql; + private final String newSql; + private final TableColumnAliasContext aliasContext; + + public DataPermissionSQL(String sql, String newSql, TableColumnAliasContext aliasContext) { + this.sql = sql; + this.newSql = newSql; + this.aliasContext = aliasContext; + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DefaultSQLInterceptor.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DefaultSQLInterceptor.java new file mode 100644 index 00000000..47bb18bf --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/DefaultSQLInterceptor.java @@ -0,0 +1,40 @@ +package com.codingapi.springboot.authorization.interceptor; + + +import com.codingapi.springboot.authorization.enhancer.DataPermissionSQLEnhancer; +import com.codingapi.springboot.authorization.handler.RowHandler; +import com.codingapi.springboot.authorization.handler.RowHandlerContext; +import com.codingapi.springboot.authorization.properties.DataAuthorizationPropertyContext; +import com.codingapi.springboot.authorization.utils.SQLUtils; +import lombok.extern.slf4j.Slf4j; + +import java.sql.SQLException; + +@Slf4j +public class DefaultSQLInterceptor implements SQLInterceptor { + + @Override + public boolean beforeHandler(String sql) { + return SQLUtils.isQuerySql(sql); + } + + + @Override + public void afterHandler(String sql, String newSql, SQLException exception) { + if(exception!=null){ + log.error("sql:{}",sql); + log.error("newSql:{}",newSql); + log.error(exception.getMessage(),exception); + } + if (DataAuthorizationPropertyContext.getInstance().showSql()) { + log.info("newSql:{}", newSql); + } + } + + @Override + public DataPermissionSQL postHandler(String sql) throws SQLException { + RowHandler rowHandler = RowHandlerContext.getInstance().getRowHandler(); + DataPermissionSQLEnhancer sqlEnhancer = new DataPermissionSQLEnhancer(sql, rowHandler); + return new DataPermissionSQL(sql, sqlEnhancer.getNewSQL(), sqlEnhancer.getTableAlias()); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptState.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptState.java new file mode 100644 index 00000000..809668fb --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptState.java @@ -0,0 +1,60 @@ +package com.codingapi.springboot.authorization.interceptor; + +import com.codingapi.springboot.authorization.enhancer.TableColumnAliasContext; + +/** + * SQL拦截状态 + */ +public class SQLInterceptState { + + private final boolean state; + + private final String sql; + + private final String newSql; + + private final TableColumnAliasContext aliasContext; + + private SQLInterceptState(boolean state, String sql, String newSql, TableColumnAliasContext aliasContext) { + this.state = state; + this.sql = sql; + this.newSql = newSql; + this.aliasContext = aliasContext; + } + + /** + * 拦截 + */ + public static SQLInterceptState intercept(String sql, String newSql, TableColumnAliasContext aliasContext) { + return new SQLInterceptState(true, sql, newSql, aliasContext); + } + + /** + * 不拦截 + */ + public static SQLInterceptState unIntercept(String sql) { + return new SQLInterceptState(false, sql, sql, null); + } + + public String getTableName(String tableName) { + return aliasContext.getTableName(tableName); + } + + public String getColumnName(String tableName, String columnName) { + return aliasContext.getColumnName(tableName, columnName); + } + + public String getSql() { + if (state) { + return newSql; + } else { + return sql; + } + } + + public boolean hasIntercept() { + return state; + } + + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptor.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptor.java new file mode 100644 index 00000000..dd627f5d --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptor.java @@ -0,0 +1,39 @@ +package com.codingapi.springboot.authorization.interceptor; + +import java.sql.SQLException; + +/** + * SQL查询条件处理器 + */ +public interface SQLInterceptor { + + + /** + * 前置处理 + * + * @param sql sql + * @return 是否处理 + */ + boolean beforeHandler(String sql); + + + /** + * 处理sql + * + * @param sql sql + * @return 处理后的sql newSql + * @throws SQLException + */ + DataPermissionSQL postHandler(String sql) throws SQLException; + + + /** + * 后置处理 + * @param sql sql + * @param newSql newSql + * @param exception exception + */ + void afterHandler(String sql, String newSql, SQLException exception); + + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptorContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptorContext.java new file mode 100644 index 00000000..178de534 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLInterceptorContext.java @@ -0,0 +1,20 @@ +package com.codingapi.springboot.authorization.interceptor; + +import lombok.Getter; +import lombok.Setter; + +@Setter +public class SQLInterceptorContext { + + @Getter + private final static SQLInterceptorContext instance = new SQLInterceptorContext(); + + @Getter + private SQLInterceptor sqlInterceptor; + + private SQLInterceptorContext() { + this.sqlInterceptor = new DefaultSQLInterceptor(); + } + + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLRunningContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLRunningContext.java new file mode 100644 index 00000000..c22d4e25 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/interceptor/SQLRunningContext.java @@ -0,0 +1,83 @@ +package com.codingapi.springboot.authorization.interceptor; + +import lombok.Getter; + +import java.sql.SQLException; +import java.util.function.Supplier; + +/** + * SQLRunningContext SQL执行拦截上下文 + */ +public class SQLRunningContext { + + @Getter + private final static SQLRunningContext instance = new SQLRunningContext(); + + private final ThreadLocal skipInterceptor = ThreadLocal.withInitial(() -> false); + + private SQLRunningContext() { + } + + /** + * 拦截SQL + * + * @param sql sql + * @return SQLInterceptState + * @throws SQLException SQLException + */ + public SQLInterceptState intercept(String sql) throws SQLException { + SQLInterceptor sqlInterceptor = SQLInterceptorContext.getInstance().getSqlInterceptor(); + + if (skipInterceptor.get()) { + return SQLInterceptState.unIntercept(sql); + } + try { + if (sqlInterceptor.beforeHandler(sql)) { + // 在拦截器中执行的查询操作将不会被拦截 + skipInterceptor.set(true); + DataPermissionSQL dataPermissionSQL = sqlInterceptor.postHandler(sql); + sqlInterceptor.afterHandler(sql, dataPermissionSQL.getNewSql(), null); + return SQLInterceptState.intercept(sql, dataPermissionSQL.getNewSql(), dataPermissionSQL.getAliasContext()); + } + } catch (SQLException exception) { + sqlInterceptor.afterHandler(sql, null, exception); + } finally { + // 重置拦截器状态 + skipInterceptor.set(false); + } + return SQLInterceptState.unIntercept(sql); + } + + + /** + * 跳过数据权限拦截 + * + * @param supplier 业务逻辑 + * @param T + * @return T + */ + public T skipDataAuthorization(Supplier supplier) { + try { + skipInterceptor.set(true); + return (T) supplier.get(); + } finally { + skipInterceptor.set(false); + } + } + + + /** + * 跳过数据权限拦截 + * + * @param runnable 业务逻辑 + */ + public void skipDataAuthorization(Runnable runnable) { + try { + skipInterceptor.set(true); + runnable.run(); + } finally { + skipInterceptor.set(false); + } + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/AuthorizationJdbcDriver.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/AuthorizationJdbcDriver.java new file mode 100644 index 00000000..93045795 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/AuthorizationJdbcDriver.java @@ -0,0 +1,57 @@ +package com.codingapi.springboot.authorization.jdbc; + + +import com.codingapi.springboot.authorization.jdbc.proxy.ConnectionProxy; + +import java.sql.*; +import java.util.Enumeration; +import java.util.Properties; +import java.util.logging.Logger; + +public class AuthorizationJdbcDriver implements Driver { + + private Driver driver; + + @Override + public Connection connect(String url, Properties info) throws SQLException { + return new ConnectionProxy(driver.connect(url, info)); + } + + @Override + public boolean acceptsURL(String url) throws SQLException { + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + if (driver.acceptsURL(url)) { + this.driver = driver; + return true; + } + } + return false; + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return driver.getPropertyInfo(url, info); + } + + @Override + public int getMajorVersion() { + return driver.getMajorVersion(); + } + + @Override + public int getMinorVersion() { + return driver.getMinorVersion(); + } + + @Override + public boolean jdbcCompliant() { + return driver.jdbcCompliant(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return driver.getParentLogger(); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/CallableStatementProxy.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/CallableStatementProxy.java new file mode 100644 index 00000000..8f2b77b7 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/CallableStatementProxy.java @@ -0,0 +1,1191 @@ +package com.codingapi.springboot.authorization.jdbc.proxy; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import com.codingapi.springboot.authorization.interceptor.SQLRunningContext; +import lombok.AllArgsConstructor; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; +import java.util.Calendar; +import java.util.Map; + +@AllArgsConstructor +public class CallableStatementProxy implements CallableStatement { + + private final CallableStatement callableStatement; + + private SQLInterceptState interceptState; + + @Override + public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType); + } + + @Override + public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType, scale); + } + + @Override + public boolean wasNull() throws SQLException { + return callableStatement.wasNull(); + } + + @Override + public String getString(int parameterIndex) throws SQLException { + return callableStatement.getString(parameterIndex); + } + + @Override + public boolean getBoolean(int parameterIndex) throws SQLException { + return callableStatement.getBoolean(parameterIndex); + } + + @Override + public byte getByte(int parameterIndex) throws SQLException { + return callableStatement.getByte(parameterIndex); + } + + @Override + public short getShort(int parameterIndex) throws SQLException { + return callableStatement.getShort(parameterIndex); + } + + @Override + public int getInt(int parameterIndex) throws SQLException { + return callableStatement.getInt(parameterIndex); + } + + @Override + public long getLong(int parameterIndex) throws SQLException { + return callableStatement.getLong(parameterIndex); + } + + @Override + public float getFloat(int parameterIndex) throws SQLException { + return callableStatement.getFloat(parameterIndex); + } + + @Override + public double getDouble(int parameterIndex) throws SQLException { + return callableStatement.getDouble(parameterIndex); + } + + @Override + public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException { + return callableStatement.getBigDecimal(parameterIndex, scale); + } + + @Override + public byte[] getBytes(int parameterIndex) throws SQLException { + return callableStatement.getBytes(parameterIndex); + } + + @Override + public Date getDate(int parameterIndex) throws SQLException { + return callableStatement.getDate(parameterIndex); + } + + @Override + public Time getTime(int parameterIndex) throws SQLException { + return callableStatement.getTime(parameterIndex); + } + + @Override + public Timestamp getTimestamp(int parameterIndex) throws SQLException { + return callableStatement.getTimestamp(parameterIndex); + } + + @Override + public Object getObject(int parameterIndex) throws SQLException { + return callableStatement.getObject(parameterIndex); + } + + @Override + public BigDecimal getBigDecimal(int parameterIndex) throws SQLException { + return callableStatement.getBigDecimal(parameterIndex); + } + + @Override + public Object getObject(int parameterIndex, Map> map) throws SQLException { + return callableStatement.getObject(parameterIndex, map); + } + + @Override + public Ref getRef(int parameterIndex) throws SQLException { + return callableStatement.getRef(parameterIndex); + } + + @Override + public Blob getBlob(int parameterIndex) throws SQLException { + return callableStatement.getBlob(parameterIndex); + } + + @Override + public Clob getClob(int parameterIndex) throws SQLException { + return callableStatement.getClob(parameterIndex); + } + + @Override + public Array getArray(int parameterIndex) throws SQLException { + return callableStatement.getArray(parameterIndex); + } + + @Override + public Date getDate(int parameterIndex, Calendar cal) throws SQLException { + return callableStatement.getDate(parameterIndex, cal); + } + + @Override + public Time getTime(int parameterIndex, Calendar cal) throws SQLException { + return callableStatement.getTime(parameterIndex, cal); + } + + @Override + public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException { + return callableStatement.getTimestamp(parameterIndex, cal); + } + + @Override + public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType, typeName); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType, scale); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType, typeName); + } + + @Override + public URL getURL(int parameterIndex) throws SQLException { + return callableStatement.getURL(parameterIndex); + } + + @Override + public void setURL(String parameterName, URL val) throws SQLException { + callableStatement.setURL(parameterName, val); + } + + @Override + public void setNull(String parameterName, int sqlType) throws SQLException { + callableStatement.setNull(parameterName, sqlType); + } + + @Override + public void setBoolean(String parameterName, boolean x) throws SQLException { + callableStatement.setBoolean(parameterName, x); + } + + @Override + public void setByte(String parameterName, byte x) throws SQLException { + callableStatement.setByte(parameterName, x); + } + + @Override + public void setShort(String parameterName, short x) throws SQLException { + callableStatement.setShort(parameterName, x); + } + + @Override + public void setInt(String parameterName, int x) throws SQLException { + callableStatement.setInt(parameterName, x); + } + + @Override + public void setLong(String parameterName, long x) throws SQLException { + callableStatement.setLong(parameterName, x); + } + + @Override + public void setFloat(String parameterName, float x) throws SQLException { + callableStatement.setFloat(parameterName, x); + } + + @Override + public void setDouble(String parameterName, double x) throws SQLException { + callableStatement.setDouble(parameterName, x); + } + + @Override + public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException { + callableStatement.setBigDecimal(parameterName, x); + } + + @Override + public void setString(String parameterName, String x) throws SQLException { + callableStatement.setString(parameterName, x); + } + + @Override + public void setBytes(String parameterName, byte[] x) throws SQLException { + callableStatement.setBytes(parameterName, x); + } + + @Override + public void setDate(String parameterName, Date x) throws SQLException { + callableStatement.setDate(parameterName, x); + } + + @Override + public void setTime(String parameterName, Time x) throws SQLException { + callableStatement.setTime(parameterName, x); + } + + @Override + public void setTimestamp(String parameterName, Timestamp x) throws SQLException { + callableStatement.setTimestamp(parameterName, x); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException { + callableStatement.setAsciiStream(parameterName, x, length); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException { + callableStatement.setBinaryStream(parameterName, x, length); + } + + @Override + public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException { + callableStatement.setObject(parameterName, x, targetSqlType, scale); + } + + @Override + public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException { + callableStatement.setObject(parameterName, x, targetSqlType); + } + + @Override + public void setObject(String parameterName, Object x) throws SQLException { + callableStatement.setObject(parameterName, x); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException { + callableStatement.setCharacterStream(parameterName, reader, length); + } + + @Override + public void setDate(String parameterName, Date x, Calendar cal) throws SQLException { + callableStatement.setDate(parameterName, x, cal); + } + + @Override + public void setTime(String parameterName, Time x, Calendar cal) throws SQLException { + callableStatement.setTime(parameterName, x, cal); + } + + @Override + public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException { + callableStatement.setTimestamp(parameterName, x, cal); + } + + @Override + public void setNull(String parameterName, int sqlType, String typeName) throws SQLException { + callableStatement.setNull(parameterName, sqlType, typeName); + } + + @Override + public String getString(String parameterName) throws SQLException { + return callableStatement.getString(parameterName); + } + + @Override + public boolean getBoolean(String parameterName) throws SQLException { + return callableStatement.getBoolean(parameterName); + } + + @Override + public byte getByte(String parameterName) throws SQLException { + return callableStatement.getByte(parameterName); + } + + @Override + public short getShort(String parameterName) throws SQLException { + return callableStatement.getShort(parameterName); + } + + @Override + public int getInt(String parameterName) throws SQLException { + return callableStatement.getInt(parameterName); + } + + @Override + public long getLong(String parameterName) throws SQLException { + return callableStatement.getLong(parameterName); + } + + @Override + public float getFloat(String parameterName) throws SQLException { + return callableStatement.getFloat(parameterName); + } + + @Override + public double getDouble(String parameterName) throws SQLException { + return callableStatement.getDouble(parameterName); + } + + @Override + public byte[] getBytes(String parameterName) throws SQLException { + return callableStatement.getBytes(parameterName); + } + + @Override + public Date getDate(String parameterName) throws SQLException { + return callableStatement.getDate(parameterName); + } + + @Override + public Time getTime(String parameterName) throws SQLException { + return callableStatement.getTime(parameterName); + } + + @Override + public Timestamp getTimestamp(String parameterName) throws SQLException { + return callableStatement.getTimestamp(parameterName); + } + + @Override + public Object getObject(String parameterName) throws SQLException { + return callableStatement.getObject(parameterName); + } + + @Override + public BigDecimal getBigDecimal(String parameterName) throws SQLException { + return callableStatement.getBigDecimal(parameterName); + } + + @Override + public Object getObject(String parameterName, Map> map) throws SQLException { + return callableStatement.getObject(parameterName, map); + } + + @Override + public Ref getRef(String parameterName) throws SQLException { + return callableStatement.getRef(parameterName); + } + + @Override + public Blob getBlob(String parameterName) throws SQLException { + return callableStatement.getBlob(parameterName); + } + + @Override + public Clob getClob(String parameterName) throws SQLException { + return callableStatement.getClob(parameterName); + } + + @Override + public Array getArray(String parameterName) throws SQLException { + return callableStatement.getArray(parameterName); + } + + @Override + public Date getDate(String parameterName, Calendar cal) throws SQLException { + return callableStatement.getDate(parameterName, cal); + } + + @Override + public Time getTime(String parameterName, Calendar cal) throws SQLException { + return callableStatement.getTime(parameterName, cal); + } + + @Override + public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException { + return callableStatement.getTimestamp(parameterName, cal); + } + + @Override + public URL getURL(String parameterName) throws SQLException { + return callableStatement.getURL(parameterName); + } + + @Override + public RowId getRowId(int parameterIndex) throws SQLException { + return callableStatement.getRowId(parameterIndex); + } + + @Override + public RowId getRowId(String parameterName) throws SQLException { + return callableStatement.getRowId(parameterName); + } + + @Override + public void setRowId(String parameterName, RowId x) throws SQLException { + callableStatement.setRowId(parameterName, x); + } + + @Override + public void setNString(String parameterName, String value) throws SQLException { + callableStatement.setNString(parameterName, value); + } + + @Override + public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException { + callableStatement.setNCharacterStream(parameterName, value, length); + } + + @Override + public void setNClob(String parameterName, NClob value) throws SQLException { + callableStatement.setNClob(parameterName, value); + } + + @Override + public void setClob(String parameterName, Reader reader, long length) throws SQLException { + callableStatement.setClob(parameterName, reader, length); + } + + @Override + public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException { + callableStatement.setBlob(parameterName, inputStream, length); + } + + @Override + public void setNClob(String parameterName, Reader reader, long length) throws SQLException { + callableStatement.setNClob(parameterName, reader, length); + } + + @Override + public NClob getNClob(int parameterIndex) throws SQLException { + return callableStatement.getNClob(parameterIndex); + } + + @Override + public NClob getNClob(String parameterName) throws SQLException { + return callableStatement.getNClob(parameterName); + } + + @Override + public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException { + callableStatement.setSQLXML(parameterName, xmlObject); + } + + @Override + public SQLXML getSQLXML(int parameterIndex) throws SQLException { + return callableStatement.getSQLXML(parameterIndex); + } + + @Override + public SQLXML getSQLXML(String parameterName) throws SQLException { + return callableStatement.getSQLXML(parameterName); + } + + @Override + public String getNString(int parameterIndex) throws SQLException { + return callableStatement.getNString(parameterIndex); + } + + @Override + public String getNString(String parameterName) throws SQLException { + return callableStatement.getNString(parameterName); + } + + @Override + public Reader getNCharacterStream(int parameterIndex) throws SQLException { + return callableStatement.getNCharacterStream(parameterIndex); + } + + @Override + public Reader getNCharacterStream(String parameterName) throws SQLException { + return callableStatement.getNCharacterStream(parameterName); + } + + @Override + public Reader getCharacterStream(int parameterIndex) throws SQLException { + return callableStatement.getCharacterStream(parameterIndex); + } + + @Override + public Reader getCharacterStream(String parameterName) throws SQLException { + return callableStatement.getCharacterStream(parameterName); + } + + @Override + public void setBlob(String parameterName, Blob x) throws SQLException { + callableStatement.setBlob(parameterName, x); + } + + @Override + public void setClob(String parameterName, Clob x) throws SQLException { + callableStatement.setClob(parameterName, x); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException { + callableStatement.setAsciiStream(parameterName, x, length); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException { + callableStatement.setBinaryStream(parameterName, x, length); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException { + callableStatement.setCharacterStream(parameterName, reader, length); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x) throws SQLException { + callableStatement.setAsciiStream(parameterName, x); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x) throws SQLException { + callableStatement.setBinaryStream(parameterName, x); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader) throws SQLException { + callableStatement.setCharacterStream(parameterName, reader); + } + + @Override + public void setNCharacterStream(String parameterName, Reader value) throws SQLException { + callableStatement.setNCharacterStream(parameterName, value); + } + + @Override + public void setClob(String parameterName, Reader reader) throws SQLException { + callableStatement.setClob(parameterName, reader); + } + + @Override + public void setBlob(String parameterName, InputStream inputStream) throws SQLException { + callableStatement.setBlob(parameterName, inputStream); + } + + @Override + public void setNClob(String parameterName, Reader reader) throws SQLException { + callableStatement.setNClob(parameterName, reader); + } + + @Override + public T getObject(int parameterIndex, Class type) throws SQLException { + return callableStatement.getObject(parameterIndex, type); + } + + @Override + public T getObject(String parameterName, Class type) throws SQLException { + return callableStatement.getObject(parameterName, type); + } + + @Override + public void setObject(String parameterName, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + callableStatement.setObject(parameterName, x, targetSqlType, scaleOrLength); + } + + @Override + public void setObject(String parameterName, Object x, SQLType targetSqlType) throws SQLException { + callableStatement.setObject(parameterName, x, targetSqlType); + } + + @Override + public void registerOutParameter(int parameterIndex, SQLType sqlType) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType); + } + + @Override + public void registerOutParameter(int parameterIndex, SQLType sqlType, int scale) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType, scale); + } + + @Override + public void registerOutParameter(int parameterIndex, SQLType sqlType, String typeName) throws SQLException { + callableStatement.registerOutParameter(parameterIndex, sqlType, typeName); + } + + @Override + public void registerOutParameter(String parameterName, SQLType sqlType) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType); + } + + @Override + public void registerOutParameter(String parameterName, SQLType sqlType, int scale) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType, scale); + } + + @Override + public void registerOutParameter(String parameterName, SQLType sqlType, String typeName) throws SQLException { + callableStatement.registerOutParameter(parameterName, sqlType, typeName); + } + + @Override + public ResultSet executeQuery() throws SQLException { + return new ResultSetProxy(callableStatement.executeQuery(),this.interceptState); + } + + @Override + public int executeUpdate() throws SQLException { + return callableStatement.executeUpdate(); + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + callableStatement.setNull(parameterIndex, sqlType); + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + callableStatement.setBoolean(parameterIndex, x); + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + callableStatement.setByte(parameterIndex, x); + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + callableStatement.setShort(parameterIndex, x); + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + callableStatement.setInt(parameterIndex, x); + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + callableStatement.setLong(parameterIndex, x); + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + callableStatement.setFloat(parameterIndex, x); + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + callableStatement.setDouble(parameterIndex, x); + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + callableStatement.setBigDecimal(parameterIndex, x); + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + callableStatement.setString(parameterIndex, x); + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + callableStatement.setBytes(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + callableStatement.setDate(parameterIndex, x); + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + callableStatement.setTime(parameterIndex, x); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + callableStatement.setTimestamp(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + callableStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + callableStatement.setUnicodeStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + callableStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void clearParameters() throws SQLException { + callableStatement.clearParameters(); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + callableStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + callableStatement.setObject(parameterIndex, x); + } + + @Override + public boolean execute() throws SQLException { + return callableStatement.execute(); + } + + @Override + public void addBatch() throws SQLException { + callableStatement.addBatch(); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + callableStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + callableStatement.setRef(parameterIndex, x); + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + callableStatement.setBlob(parameterIndex, x); + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + callableStatement.setClob(parameterIndex, x); + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + callableStatement.setArray(parameterIndex, x); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return callableStatement.getMetaData(); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + callableStatement.setDate(parameterIndex, x, cal); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + callableStatement.setTime(parameterIndex, x, cal); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + callableStatement.setTimestamp(parameterIndex, x, cal); + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + callableStatement.setNull(parameterIndex, sqlType, typeName); + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + callableStatement.setURL(parameterIndex, x); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return callableStatement.getParameterMetaData(); + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + callableStatement.setRowId(parameterIndex, x); + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + callableStatement.setNString(parameterIndex, value); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + callableStatement.setNCharacterStream(parameterIndex, value, length); + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + callableStatement.setNClob(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + callableStatement.setClob(parameterIndex, reader, length); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + callableStatement.setBlob(parameterIndex, inputStream, length); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + callableStatement.setNClob(parameterIndex, reader, length); + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + callableStatement.setSQLXML(parameterIndex, xmlObject); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { + callableStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + callableStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + callableStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + callableStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + callableStatement.setAsciiStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + callableStatement.setBinaryStream(parameterIndex, x); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + callableStatement.setCharacterStream(parameterIndex, reader); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + callableStatement.setNCharacterStream(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + callableStatement.setClob(parameterIndex, reader); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + callableStatement.setBlob(parameterIndex, inputStream); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + callableStatement.setNClob(parameterIndex, reader); + } + + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + callableStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { + callableStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public long executeLargeUpdate() throws SQLException { + return callableStatement.executeLargeUpdate(); + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new ResultSetProxy(callableStatement.executeQuery(interceptState.getSql()),this.interceptState); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeUpdate(interceptState.getSql()); + } + + @Override + public void close() throws SQLException { + callableStatement.close(); + } + + @Override + public int getMaxFieldSize() throws SQLException { + return callableStatement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + callableStatement.setMaxFieldSize(max); + } + + @Override + public int getMaxRows() throws SQLException { + return callableStatement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + callableStatement.setMaxRows(max); + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + callableStatement.setEscapeProcessing(enable); + } + + @Override + public int getQueryTimeout() throws SQLException { + return callableStatement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + callableStatement.setQueryTimeout(seconds); + } + + @Override + public void cancel() throws SQLException { + callableStatement.cancel(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return callableStatement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + callableStatement.clearWarnings(); + } + + @Override + public void setCursorName(String name) throws SQLException { + callableStatement.setCursorName(name); + } + + @Override + public boolean execute(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.execute(interceptState.getSql()); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return new ResultSetProxy(callableStatement.getResultSet(),this.interceptState); + } + + @Override + public int getUpdateCount() throws SQLException { + return callableStatement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return callableStatement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + callableStatement.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return callableStatement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + callableStatement.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return callableStatement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return callableStatement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return callableStatement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + callableStatement.addBatch(interceptState.getSql()); + } + + @Override + public void clearBatch() throws SQLException { + callableStatement.clearBatch(); + } + + @Override + public int[] executeBatch() throws SQLException { + return callableStatement.executeBatch(); + } + + @Override + public Connection getConnection() throws SQLException { + return new ConnectionProxy(callableStatement.getConnection()); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return callableStatement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return callableStatement.getGeneratedKeys(); + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeUpdate(interceptState.getSql(), columnNames); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.execute(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.execute(interceptState.getSql(), columnIndexes); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.execute(interceptState.getSql(), columnNames); + } + + @Override + public int getResultSetHoldability() throws SQLException { + return callableStatement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return callableStatement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + callableStatement.setPoolable(poolable); + } + + @Override + public boolean isPoolable() throws SQLException { + return callableStatement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + callableStatement.closeOnCompletion(); + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return callableStatement.isCloseOnCompletion(); + } + + @Override + public long getLargeUpdateCount() throws SQLException { + return callableStatement.getLargeUpdateCount(); + } + + @Override + public void setLargeMaxRows(long max) throws SQLException { + callableStatement.setLargeMaxRows(max); + } + + @Override + public long getLargeMaxRows() throws SQLException { + return callableStatement.getLargeMaxRows(); + } + + @Override + public long[] executeLargeBatch() throws SQLException { + return callableStatement.executeLargeBatch(); + } + + @Override + public long executeLargeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeLargeUpdate(interceptState.getSql()); + } + + @Override + public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeLargeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeLargeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return callableStatement.executeLargeUpdate(interceptState.getSql(), columnNames); + } + + + @Override + public T unwrap(Class iface) throws SQLException { + return callableStatement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return callableStatement.isWrapperFor(iface); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ConnectionProxy.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ConnectionProxy.java new file mode 100644 index 00000000..72a09567 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ConnectionProxy.java @@ -0,0 +1,300 @@ +package com.codingapi.springboot.authorization.jdbc.proxy; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import com.codingapi.springboot.authorization.interceptor.SQLRunningContext; + +import java.sql.*; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +public class ConnectionProxy implements Connection { + + private final Connection connection; + + public ConnectionProxy(Connection connection) { + this.connection = connection; + } + + private SQLInterceptState interceptState; + + @Override + public Statement createStatement() throws SQLException { + return new StatementProxy(connection.createStatement(), interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql()),interceptState); + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new CallableStatementProxy(connection.prepareCall(interceptState.getSql()), interceptState); + } + + @Override + public String nativeSQL(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return connection.nativeSQL(interceptState.getSql()); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + connection.setAutoCommit(autoCommit); + } + + @Override + public boolean getAutoCommit() throws SQLException { + return connection.getAutoCommit(); + } + + @Override + public void commit() throws SQLException { + connection.commit(); + } + + @Override + public void rollback() throws SQLException { + connection.rollback(); + } + + @Override + public void close() throws SQLException { + connection.close(); + } + + @Override + public boolean isClosed() throws SQLException { + return connection.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return connection.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + connection.setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() throws SQLException { + return connection.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + connection.setCatalog(catalog); + } + + @Override + public String getCatalog() throws SQLException { + return connection.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + connection.setTransactionIsolation(level); + } + + @Override + public int getTransactionIsolation() throws SQLException { + return connection.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return connection.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + connection.clearWarnings(); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + return new StatementProxy(connection.createStatement(resultSetType, resultSetConcurrency),this.interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql(), resultSetType, resultSetConcurrency),interceptState); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new CallableStatementProxy(connection.prepareCall(interceptState.getSql(), resultSetType, resultSetConcurrency),interceptState); + } + + @Override + public Map> getTypeMap() throws SQLException { + return connection.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + connection.setTypeMap(map); + } + + @Override + public void setHoldability(int holdability) throws SQLException { + connection.setHoldability(holdability); + } + + @Override + public int getHoldability() throws SQLException { + return connection.getHoldability(); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return connection.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return connection.setSavepoint(name); + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + connection.rollback(savepoint); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + connection.releaseSavepoint(savepoint); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + return new StatementProxy(connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability),interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql(), resultSetType, resultSetConcurrency, resultSetHoldability),interceptState); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new CallableStatementProxy(connection.prepareCall(interceptState.getSql(), resultSetType, resultSetConcurrency, resultSetHoldability),interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql(), autoGeneratedKeys),interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql(), columnIndexes),interceptState); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new PreparedStatementProxy(connection.prepareStatement(interceptState.getSql(), columnNames),interceptState); + } + + @Override + public Clob createClob() throws SQLException { + return connection.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return connection.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return connection.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return connection.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return connection.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + connection.setClientInfo(name, value); + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + connection.setClientInfo(properties); + } + + @Override + public String getClientInfo(String name) throws SQLException { + return connection.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return connection.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return connection.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return connection.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + connection.setSchema(schema); + } + + @Override + public String getSchema() throws SQLException { + return connection.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + connection.abort(executor); + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + connection.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return connection.getNetworkTimeout(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return connection.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return connection.isWrapperFor(iface); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/PreparedStatementProxy.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/PreparedStatementProxy.java new file mode 100644 index 00000000..89c17acd --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/PreparedStatementProxy.java @@ -0,0 +1,585 @@ +package com.codingapi.springboot.authorization.jdbc.proxy; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import com.codingapi.springboot.authorization.interceptor.SQLRunningContext; +import lombok.AllArgsConstructor; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; +import java.util.Calendar; + +@AllArgsConstructor +public class PreparedStatementProxy implements PreparedStatement { + + private final PreparedStatement preparedStatement; + + private SQLInterceptState interceptState; + + + @Override + public ResultSet executeQuery() throws SQLException { + return new ResultSetProxy(preparedStatement.executeQuery(),interceptState); + } + + @Override + public int executeUpdate() throws SQLException { + return preparedStatement.executeUpdate(); + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + preparedStatement.setNull(parameterIndex, sqlType); + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + preparedStatement.setBoolean(parameterIndex, x); + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + preparedStatement.setByte(parameterIndex, x); + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + preparedStatement.setShort(parameterIndex, x); + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + preparedStatement.setInt(parameterIndex, x); + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + preparedStatement.setLong(parameterIndex, x); + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + preparedStatement.setFloat(parameterIndex, x); + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + preparedStatement.setDouble(parameterIndex, x); + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + preparedStatement.setBigDecimal(parameterIndex, x); + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + preparedStatement.setString(parameterIndex, x); + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + preparedStatement.setBytes(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + preparedStatement.setDate(parameterIndex, x); + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + preparedStatement.setTime(parameterIndex, x); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + preparedStatement.setTimestamp(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setUnicodeStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void clearParameters() throws SQLException { + preparedStatement.clearParameters(); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + preparedStatement.setObject(parameterIndex, x); + } + + @Override + public boolean execute() throws SQLException { + return preparedStatement.execute(); + } + + @Override + public void addBatch() throws SQLException { + preparedStatement.addBatch(); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + preparedStatement.setRef(parameterIndex, x); + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + preparedStatement.setBlob(parameterIndex, x); + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + preparedStatement.setClob(parameterIndex, x); + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + preparedStatement.setArray(parameterIndex, x); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return preparedStatement.getMetaData(); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + preparedStatement.setDate(parameterIndex, x, cal); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + preparedStatement.setTime(parameterIndex, x, cal); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + preparedStatement.setTimestamp(parameterIndex, x, cal); + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + preparedStatement.setNull(parameterIndex, sqlType, typeName); + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + preparedStatement.setURL(parameterIndex, x); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return preparedStatement.getParameterMetaData(); + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + preparedStatement.setRowId(parameterIndex, x); + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + preparedStatement.setNString(parameterIndex, value); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + preparedStatement.setNCharacterStream(parameterIndex, value, length); + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + preparedStatement.setNClob(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + preparedStatement.setClob(parameterIndex, reader, length); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + preparedStatement.setBlob(parameterIndex, inputStream, length); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + preparedStatement.setNClob(parameterIndex, reader, length); + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + preparedStatement.setSQLXML(parameterIndex, xmlObject); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + preparedStatement.setNCharacterStream(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setClob(parameterIndex, reader); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + preparedStatement.setBlob(parameterIndex, inputStream); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setNClob(parameterIndex, reader); + } + + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public long executeLargeUpdate() throws SQLException { + return preparedStatement.executeLargeUpdate(); + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new ResultSetProxy(preparedStatement.executeQuery(interceptState.getSql()),interceptState); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeUpdate(interceptState.getSql()); + } + + @Override + public void close() throws SQLException { + preparedStatement.close(); + } + + @Override + public int getMaxFieldSize() throws SQLException { + return preparedStatement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + preparedStatement.setMaxFieldSize(max); + } + + @Override + public int getMaxRows() throws SQLException { + return preparedStatement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + preparedStatement.setMaxRows(max); + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + preparedStatement.setEscapeProcessing(enable); + } + + @Override + public int getQueryTimeout() throws SQLException { + return preparedStatement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + preparedStatement.setQueryTimeout(seconds); + } + + @Override + public void cancel() throws SQLException { + preparedStatement.cancel(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return preparedStatement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + preparedStatement.clearWarnings(); + } + + @Override + public void setCursorName(String name) throws SQLException { + preparedStatement.setCursorName(name); + } + + @Override + public boolean execute(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.execute(interceptState.getSql()); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return new ResultSetProxy(preparedStatement.getResultSet(),interceptState); + } + + @Override + public int getUpdateCount() throws SQLException { + return preparedStatement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return preparedStatement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + preparedStatement.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return preparedStatement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + preparedStatement.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return preparedStatement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return preparedStatement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return preparedStatement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + preparedStatement.addBatch(interceptState.getSql()); + } + + @Override + public void clearBatch() throws SQLException { + preparedStatement.clearBatch(); + } + + @Override + public int[] executeBatch() throws SQLException { + return preparedStatement.executeBatch(); + } + + @Override + public Connection getConnection() throws SQLException { + return new ConnectionProxy(preparedStatement.getConnection()); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return preparedStatement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return new ResultSetProxy(preparedStatement.getGeneratedKeys(),interceptState); + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeUpdate(interceptState.getSql(), columnNames); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.execute(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.execute(interceptState.getSql(), columnIndexes); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.execute(interceptState.getSql(), columnNames); + } + + @Override + public int getResultSetHoldability() throws SQLException { + return preparedStatement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return preparedStatement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + preparedStatement.setPoolable(poolable); + } + + @Override + public boolean isPoolable() throws SQLException { + return preparedStatement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + preparedStatement.closeOnCompletion(); + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return preparedStatement.isCloseOnCompletion(); + } + + @Override + public long getLargeUpdateCount() throws SQLException { + return preparedStatement.getLargeUpdateCount(); + } + + @Override + public void setLargeMaxRows(long max) throws SQLException { + preparedStatement.setLargeMaxRows(max); + } + + @Override + public long getLargeMaxRows() throws SQLException { + return preparedStatement.getLargeMaxRows(); + } + + @Override + public long[] executeLargeBatch() throws SQLException { + return preparedStatement.executeLargeBatch(); + } + + @Override + public long executeLargeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeLargeUpdate(interceptState.getSql()); + } + + @Override + public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeLargeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeLargeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return preparedStatement.executeLargeUpdate(interceptState.getSql(), columnNames); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return preparedStatement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return preparedStatement.isWrapperFor(iface); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ResultSetProxy.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ResultSetProxy.java new file mode 100644 index 00000000..40c72b09 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/ResultSetProxy.java @@ -0,0 +1,1180 @@ +package com.codingapi.springboot.authorization.jdbc.proxy; + +import com.codingapi.springboot.authorization.handler.ColumnHandlerContext; +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import lombok.extern.slf4j.Slf4j; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.*; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class ResultSetProxy implements ResultSet { + + private final ResultSet resultSet; + private final ResultSetMetaData metaData; + private final SQLInterceptState interceptState; + + private final Map columnLabelMap = new HashMap<>(); + + public ResultSetProxy(ResultSet resultSet, SQLInterceptState interceptState) throws SQLException { + this.resultSet = resultSet; + this.metaData = resultSet.getMetaData(); + this.interceptState = interceptState; + int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + String columnLabel = metaData.getColumnLabel(i); + columnLabelMap.put(columnLabel.toUpperCase(), i); + } + } + + @Override + public boolean next() throws SQLException { + return resultSet.next(); + } + + @Override + public void close() throws SQLException { + resultSet.close(); + } + + @Override + public boolean wasNull() throws SQLException { + return resultSet.wasNull(); + } + + @Override + public String getString(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getString(interceptState, columnIndex, tableName, columnName, resultSet.getString(columnIndex)); + } + + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBoolean(interceptState, columnIndex, tableName, columnName, resultSet.getBoolean(columnIndex)); + } + + @Override + public byte getByte(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getByte(interceptState, columnIndex, tableName, columnName, resultSet.getByte(columnIndex)); + } + + @Override + public short getShort(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getShort(interceptState, columnIndex, tableName, columnName, resultSet.getShort(columnIndex)); + } + + @Override + public int getInt(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getInt(interceptState, columnIndex, tableName, columnName, resultSet.getInt(columnIndex)); + } + + @Override + public long getLong(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getLong(interceptState, columnIndex, tableName, columnName, resultSet.getLong(columnIndex)); + } + + @Override + public float getFloat(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getFloat(interceptState, columnIndex, tableName, columnName, resultSet.getFloat(columnIndex)); + } + + @Override + public double getDouble(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDouble(interceptState, columnIndex, tableName, columnName, resultSet.getDouble(columnIndex)); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBigDecimal(interceptState, columnIndex, tableName, columnName, resultSet.getBigDecimal(columnIndex, scale)); + } + + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBytes(interceptState, columnIndex, tableName, columnName, resultSet.getBytes(columnIndex)); + } + + @Override + public Date getDate(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDate(interceptState, columnIndex, tableName, columnName, resultSet.getDate(columnIndex)); + } + + @Override + public Time getTime(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTime(interceptState, columnIndex, tableName, columnName, resultSet.getTime(columnIndex)); + } + + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTimestamp(interceptState, columnIndex, tableName, columnName, resultSet.getTimestamp(columnIndex)); + } + + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getAsciiStream(interceptState, columnIndex, tableName, columnName, resultSet.getAsciiStream(columnIndex)); + } + + @Override + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getUnicodeStream(interceptState, columnIndex, tableName, columnName, resultSet.getUnicodeStream(columnIndex)); + } + + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBinaryStream(interceptState, columnIndex, tableName, columnName, resultSet.getBinaryStream(columnIndex)); + } + + @Override + public String getString(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getString(interceptState, columnIndex, tableName, columnName, resultSet.getString(columnIndex)); + } + + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBoolean(interceptState, columnIndex, tableName, columnName, resultSet.getBoolean(columnIndex)); + } + + @Override + public byte getByte(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getByte(interceptState, columnIndex, tableName, columnName, resultSet.getByte(columnIndex)); + } + + @Override + public short getShort(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getShort(interceptState, columnIndex, tableName, columnName, resultSet.getShort(columnIndex)); + } + + @Override + public int getInt(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getInt(interceptState, columnIndex, tableName, columnName, resultSet.getInt(columnIndex)); + } + + @Override + public long getLong(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getLong(interceptState, columnIndex, tableName, columnName, resultSet.getLong(columnIndex)); + } + + @Override + public float getFloat(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getFloat(interceptState, columnIndex, tableName, columnName, resultSet.getFloat(columnIndex)); + } + + @Override + public double getDouble(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDouble(interceptState, columnIndex, tableName, columnName, resultSet.getDouble(columnIndex)); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBigDecimal(interceptState, columnIndex, tableName, columnName, resultSet.getBigDecimal(columnIndex, scale)); + } + + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBytes(interceptState, columnIndex, tableName, columnName, resultSet.getBytes(columnIndex)); + } + + @Override + public Date getDate(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDate(interceptState, columnIndex, tableName, columnName, resultSet.getDate(columnIndex)); + } + + @Override + public Time getTime(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTime(interceptState, columnIndex, tableName, columnName, resultSet.getTime(columnIndex)); + } + + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTimestamp(interceptState, columnIndex, tableName, columnName, resultSet.getTimestamp(columnIndex)); + } + + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getAsciiStream(interceptState, columnIndex, tableName, columnName, resultSet.getAsciiStream(columnIndex)); + } + + @Override + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getUnicodeStream(interceptState, columnIndex, tableName, columnName, resultSet.getUnicodeStream(columnIndex)); + } + + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBinaryStream(interceptState, columnIndex, tableName, columnName, resultSet.getBinaryStream(columnIndex)); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return resultSet.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + resultSet.clearWarnings(); + } + + @Override + public String getCursorName() throws SQLException { + return resultSet.getCursorName(); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return resultSet.getMetaData(); + } + + @Override + public Object getObject(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getObject(columnIndex)); + } + + @Override + public Object getObject(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getObject(columnIndex)); + } + + @Override + public int findColumn(String columnLabel) throws SQLException { + return resultSet.findColumn(columnLabel); + } + + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getCharacterStream(interceptState, columnIndex, tableName, columnName, resultSet.getCharacterStream(columnIndex)); + } + + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getCharacterStream(interceptState, columnIndex, tableName, columnName, resultSet.getCharacterStream(columnIndex)); + } + + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBigDecimal(interceptState, columnIndex, tableName, columnName, resultSet.getBigDecimal(columnIndex)); + } + + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBigDecimal(interceptState, columnIndex, tableName, columnName, resultSet.getBigDecimal(columnIndex)); + } + + @Override + public boolean isBeforeFirst() throws SQLException { + return resultSet.isBeforeFirst(); + } + + @Override + public boolean isAfterLast() throws SQLException { + return resultSet.isAfterLast(); + } + + @Override + public boolean isFirst() throws SQLException { + return resultSet.isFirst(); + } + + @Override + public boolean isLast() throws SQLException { + return resultSet.isLast(); + } + + @Override + public void beforeFirst() throws SQLException { + resultSet.beforeFirst(); + } + + @Override + public void afterLast() throws SQLException { + resultSet.afterLast(); + } + + @Override + public boolean first() throws SQLException { + return resultSet.first(); + } + + @Override + public boolean last() throws SQLException { + return resultSet.last(); + } + + @Override + public int getRow() throws SQLException { + return resultSet.getRow(); + } + + @Override + public boolean absolute(int row) throws SQLException { + return resultSet.absolute(row); + } + + @Override + public boolean relative(int rows) throws SQLException { + return resultSet.relative(rows); + } + + @Override + public boolean previous() throws SQLException { + return resultSet.previous(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + resultSet.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return resultSet.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + resultSet.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return resultSet.getFetchSize(); + } + + @Override + public int getType() throws SQLException { + return resultSet.getType(); + } + + @Override + public int getConcurrency() throws SQLException { + return resultSet.getConcurrency(); + } + + @Override + public boolean rowUpdated() throws SQLException { + return resultSet.rowUpdated(); + } + + @Override + public boolean rowInserted() throws SQLException { + return resultSet.rowInserted(); + } + + @Override + public boolean rowDeleted() throws SQLException { + return resultSet.rowDeleted(); + } + + @Override + public void updateNull(int columnIndex) throws SQLException { + resultSet.updateNull(columnIndex); + } + + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + resultSet.updateBoolean(columnIndex, x); + } + + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + resultSet.updateByte(columnIndex, x); + } + + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + resultSet.updateShort(columnIndex, x); + } + + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + resultSet.updateInt(columnIndex, x); + } + + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + resultSet.updateLong(columnIndex, x); + } + + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + resultSet.updateFloat(columnIndex, x); + } + + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + resultSet.updateDouble(columnIndex, x); + } + + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + resultSet.updateBigDecimal(columnIndex, x); + } + + @Override + public void updateString(int columnIndex, String x) throws SQLException { + resultSet.updateString(columnIndex, x); + } + + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + resultSet.updateBytes(columnIndex, x); + } + + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + resultSet.updateDate(columnIndex, x); + } + + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + resultSet.updateTime(columnIndex, x); + } + + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + resultSet.updateTimestamp(columnIndex, x); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + resultSet.updateAsciiStream(columnIndex, x, length); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + resultSet.updateBinaryStream(columnIndex, x, length); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + resultSet.updateCharacterStream(columnIndex, x, length); + } + + @Override + public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException { + resultSet.updateObject(columnIndex, x, scaleOrLength); + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + resultSet.updateObject(columnIndex, x); + } + + @Override + public void updateNull(String columnLabel) throws SQLException { + resultSet.updateNull(columnLabel); + } + + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException { + resultSet.updateBoolean(columnLabel, x); + } + + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + resultSet.updateByte(columnLabel, x); + } + + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + resultSet.updateShort(columnLabel, x); + } + + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + resultSet.updateInt(columnLabel, x); + } + + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + resultSet.updateLong(columnLabel, x); + } + + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + resultSet.updateFloat(columnLabel, x); + } + + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + resultSet.updateDouble(columnLabel, x); + } + + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { + resultSet.updateBigDecimal(columnLabel, x); + } + + @Override + public void updateString(String columnLabel, String x) throws SQLException { + resultSet.updateString(columnLabel, x); + } + + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + resultSet.updateBytes(columnLabel, x); + } + + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + resultSet.updateDate(columnLabel, x); + } + + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + resultSet.updateTime(columnLabel, x); + } + + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { + resultSet.updateTimestamp(columnLabel, x); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + resultSet.updateAsciiStream(columnLabel, x, length); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + resultSet.updateBinaryStream(columnLabel, x, length); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException { + resultSet.updateCharacterStream(columnLabel, reader, length); + } + + @Override + public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException { + resultSet.updateObject(columnLabel, x, scaleOrLength); + } + + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + resultSet.updateObject(columnLabel, x); + } + + @Override + public void insertRow() throws SQLException { + resultSet.insertRow(); + } + + @Override + public void updateRow() throws SQLException { + resultSet.updateRow(); + } + + @Override + public void deleteRow() throws SQLException { + resultSet.deleteRow(); + } + + @Override + public void refreshRow() throws SQLException { + resultSet.refreshRow(); + } + + @Override + public void cancelRowUpdates() throws SQLException { + resultSet.cancelRowUpdates(); + } + + @Override + public void moveToInsertRow() throws SQLException { + resultSet.moveToInsertRow(); + } + + @Override + public void moveToCurrentRow() throws SQLException { + resultSet.moveToCurrentRow(); + } + + @Override + public Statement getStatement() throws SQLException { + return resultSet.getStatement(); + } + + @Override + public Object getObject(int columnIndex, Map> map) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getDate(columnIndex)); + } + + @Override + public Ref getRef(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getRef(interceptState, columnIndex, tableName, columnName, resultSet.getRef(columnIndex)); + } + + @Override + public Blob getBlob(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBlob(interceptState, columnIndex, tableName, columnName, resultSet.getBlob(columnIndex)); + } + + @Override + public Clob getClob(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getClob(interceptState, columnIndex, tableName, columnName, resultSet.getClob(columnIndex)); + } + + @Override + public Array getArray(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getArray(interceptState, columnIndex, tableName, columnName, resultSet.getArray(columnIndex)); + } + + @Override + public Object getObject(String columnLabel, Map> map) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getObject(columnIndex)); + } + + @Override + public Ref getRef(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getRef(interceptState, columnIndex, tableName, columnName, resultSet.getRef(columnIndex)); + } + + @Override + public Blob getBlob(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getBlob(interceptState, columnIndex, tableName, columnName, resultSet.getBlob(columnIndex)); + } + + @Override + public Clob getClob(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getClob(interceptState, columnIndex, tableName, columnName, resultSet.getClob(columnIndex)); + } + + @Override + public Array getArray(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getArray(interceptState, columnIndex, tableName, columnName, resultSet.getArray(columnIndex)); + } + + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDate(interceptState, columnIndex, tableName, columnName, resultSet.getDate(columnIndex, cal)); + } + + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getDate(interceptState, columnIndex, tableName, columnName, resultSet.getDate(columnIndex, cal)); + } + + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTime(interceptState, columnIndex, tableName, columnName, resultSet.getTime(columnIndex, cal)); + } + + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTime(interceptState, columnIndex, tableName, columnName, resultSet.getTime(columnIndex, cal)); + } + + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTimestamp(interceptState, columnIndex, tableName, columnName, resultSet.getTimestamp(columnIndex, cal)); + } + + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getTimestamp(interceptState, columnIndex, tableName, columnName, resultSet.getTimestamp(columnIndex, cal)); + } + + @Override + public URL getURL(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getURL(interceptState, columnIndex, tableName, columnName, resultSet.getURL(columnIndex)); + } + + @Override + public URL getURL(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getURL(interceptState, columnIndex, tableName, columnName, resultSet.getURL(columnIndex)); + } + + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + resultSet.updateRef(columnIndex, x); + } + + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + resultSet.updateRef(columnLabel, x); + } + + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + resultSet.updateBlob(columnIndex, x); + } + + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + resultSet.updateBlob(columnLabel, x); + } + + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + resultSet.updateClob(columnIndex, x); + } + + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + resultSet.updateClob(columnLabel, x); + } + + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + resultSet.updateArray(columnIndex, x); + } + + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + resultSet.updateArray(columnLabel, x); + } + + @Override + public RowId getRowId(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getRowId(interceptState, columnIndex, tableName, columnName, resultSet.getRowId(columnIndex)); + } + + @Override + public RowId getRowId(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getRowId(interceptState, columnIndex, tableName, columnName, resultSet.getRowId(columnIndex)); + } + + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + resultSet.updateRowId(columnIndex, x); + } + + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + resultSet.updateRowId(columnLabel, x); + } + + @Override + public int getHoldability() throws SQLException { + return resultSet.getHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return resultSet.isClosed(); + } + + @Override + public void updateNString(int columnIndex, String nString) throws SQLException { + resultSet.updateNString(columnIndex, nString); + } + + @Override + public void updateNString(String columnLabel, String nString) throws SQLException { + resultSet.updateNString(columnLabel, nString); + } + + @Override + public void updateNClob(int columnIndex, NClob nClob) throws SQLException { + resultSet.updateNClob(columnIndex, nClob); + } + + @Override + public void updateNClob(String columnLabel, NClob nClob) throws SQLException { + resultSet.updateNClob(columnLabel, nClob); + } + + @Override + public NClob getNClob(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNClob(interceptState, columnIndex, tableName, columnName, resultSet.getNClob(columnIndex)); + } + + @Override + public NClob getNClob(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNClob(interceptState, columnIndex, tableName, columnName, resultSet.getNClob(columnIndex)); + } + + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getSQLXML(interceptState, columnIndex, tableName, columnName, resultSet.getSQLXML(columnIndex)); + } + + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getSQLXML(interceptState, columnIndex, tableName, columnName, resultSet.getSQLXML(columnIndex)); + } + + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { + resultSet.updateSQLXML(columnIndex, xmlObject); + } + + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { + resultSet.updateSQLXML(columnLabel, xmlObject); + } + + @Override + public String getNString(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNString(interceptState, columnIndex, tableName, columnName, resultSet.getNString(columnIndex)); + } + + @Override + public String getNString(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNString(interceptState, columnIndex, tableName, columnName, resultSet.getNString(columnIndex)); + } + + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNCharacterStream(interceptState, columnIndex, tableName, columnName, resultSet.getNCharacterStream(columnIndex)); + } + + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getNCharacterStream(interceptState, columnIndex, tableName, columnName, resultSet.getNCharacterStream(columnIndex)); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + resultSet.updateNCharacterStream(columnIndex, x, length); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + resultSet.updateNCharacterStream(columnLabel, reader, length); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + resultSet.updateAsciiStream(columnIndex, x, length); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + resultSet.updateBinaryStream(columnIndex, x, length); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + resultSet.updateCharacterStream(columnIndex, x, length); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + resultSet.updateAsciiStream(columnLabel, x, length); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + resultSet.updateBinaryStream(columnLabel, x, length); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException { + resultSet.updateCharacterStream(columnLabel, reader, length); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException { + resultSet.updateBlob(columnIndex, inputStream, length); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException { + resultSet.updateBlob(columnLabel, inputStream, length); + } + + @Override + public void updateClob(int columnIndex, Reader reader, long length) throws SQLException { + resultSet.updateClob(columnIndex, reader, length); + } + + @Override + public void updateClob(String columnLabel, Reader reader, long length) throws SQLException { + resultSet.updateClob(columnLabel, reader, length); + } + + @Override + public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException { + resultSet.updateNClob(columnIndex, reader, length); + } + + @Override + public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException { + resultSet.updateNClob(columnLabel, reader, length); + } + + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + resultSet.updateNCharacterStream(columnIndex, x); + } + + @Override + public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException { + resultSet.updateNCharacterStream(columnLabel, reader); + } + + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + resultSet.updateAsciiStream(columnIndex, x); + } + + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + resultSet.updateBinaryStream(columnIndex, x); + } + + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + resultSet.updateCharacterStream(columnIndex, x); + } + + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + resultSet.updateAsciiStream(columnLabel, x); + } + + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { + resultSet.updateBinaryStream(columnLabel, x); + } + + @Override + public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException { + resultSet.updateCharacterStream(columnLabel, reader); + } + + @Override + public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException { + resultSet.updateBlob(columnIndex, inputStream); + } + + @Override + public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException { + resultSet.updateBlob(columnLabel, inputStream); + } + + @Override + public void updateClob(int columnIndex, Reader reader) throws SQLException { + resultSet.updateClob(columnIndex, reader); + } + + @Override + public void updateClob(String columnLabel, Reader reader) throws SQLException { + resultSet.updateClob(columnLabel, reader); + } + + @Override + public void updateNClob(int columnIndex, Reader reader) throws SQLException { + resultSet.updateNClob(columnIndex, reader); + } + + @Override + public void updateNClob(String columnLabel, Reader reader) throws SQLException { + resultSet.updateNClob(columnLabel, reader); + } + + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getObject(columnIndex, type), type); + } + + @Override + public T getObject(String columnLabel, Class type) throws SQLException { + int columnIndex = columnLabelMap.get(columnLabel.toUpperCase()); + String tableName = metaData.getTableName(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + return ColumnHandlerContext.getInstance().getObject(interceptState, columnIndex, tableName, columnName, resultSet.getObject(columnIndex, type), type); + } + + @Override + public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + resultSet.updateObject(columnIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + resultSet.updateObject(columnLabel, x, targetSqlType, scaleOrLength); + } + + @Override + public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException { + resultSet.updateObject(columnIndex, x, targetSqlType); + } + + @Override + public void updateObject(String columnLabel, Object x, SQLType targetSqlType) throws SQLException { + resultSet.updateObject(columnLabel, x, targetSqlType); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return resultSet.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return resultSet.isWrapperFor(iface); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/StatementProxy.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/StatementProxy.java new file mode 100644 index 00000000..99af9be9 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/jdbc/proxy/StatementProxy.java @@ -0,0 +1,290 @@ +package com.codingapi.springboot.authorization.jdbc.proxy; + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptState; +import com.codingapi.springboot.authorization.interceptor.SQLRunningContext; +import lombok.AllArgsConstructor; + +import java.sql.*; + +@AllArgsConstructor +public class StatementProxy implements Statement { + + private final Statement statement; + private SQLInterceptState interceptState; + + + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return new ResultSetProxy(statement.executeQuery(interceptState.getSql()), interceptState); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeUpdate(interceptState.getSql()); + } + + @Override + public void close() throws SQLException { + statement.close(); + } + + @Override + public int getMaxFieldSize() throws SQLException { + return statement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + statement.setMaxFieldSize(max); + } + + @Override + public int getMaxRows() throws SQLException { + return statement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + statement.setMaxRows(max); + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + statement.setEscapeProcessing(enable); + } + + @Override + public int getQueryTimeout() throws SQLException { + return statement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + statement.setQueryTimeout(seconds); + } + + @Override + public void cancel() throws SQLException { + statement.cancel(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return statement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + statement.clearWarnings(); + } + + @Override + public void setCursorName(String name) throws SQLException { + statement.setCursorName(name); + } + + @Override + public boolean execute(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.execute(interceptState.getSql()); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return new ResultSetProxy(statement.getResultSet(), interceptState); + } + + @Override + public int getUpdateCount() throws SQLException { + return statement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return statement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + statement.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return statement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + statement.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return statement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return statement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return statement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + statement.addBatch(interceptState.getSql()); + } + + @Override + public void clearBatch() throws SQLException { + statement.clearBatch(); + } + + @Override + public int[] executeBatch() throws SQLException { + return statement.executeBatch(); + } + + @Override + public Connection getConnection() throws SQLException { + return new ConnectionProxy(statement.getConnection()); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return statement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return new ResultSetProxy(statement.getGeneratedKeys(),this.interceptState); + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeUpdate(interceptState.getSql(), columnNames); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.execute(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.execute(interceptState.getSql(), columnIndexes); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.execute(interceptState.getSql(), columnNames); + } + + @Override + public int getResultSetHoldability() throws SQLException { + return statement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return statement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + statement.setPoolable(poolable); + } + + @Override + public boolean isPoolable() throws SQLException { + return statement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + statement.closeOnCompletion(); + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return statement.isCloseOnCompletion(); + } + + @Override + public long getLargeUpdateCount() throws SQLException { + return statement.getLargeUpdateCount(); + } + + @Override + public void setLargeMaxRows(long max) throws SQLException { + statement.setLargeMaxRows(max); + } + + @Override + public long getLargeMaxRows() throws SQLException { + return statement.getLargeMaxRows(); + } + + @Override + public long[] executeLargeBatch() throws SQLException { + return statement.executeLargeBatch(); + } + + @Override + public long executeLargeUpdate(String sql) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeLargeUpdate(interceptState.getSql()); + } + + @Override + public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeLargeUpdate(interceptState.getSql(), autoGeneratedKeys); + } + + @Override + public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeLargeUpdate(interceptState.getSql(), columnIndexes); + } + + @Override + public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException { + this.interceptState = SQLRunningContext.getInstance().intercept(sql); + return statement.executeLargeUpdate(interceptState.getSql(), columnNames); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return statement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return statement.isWrapperFor(iface); + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMask.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMask.java new file mode 100644 index 00000000..c0721ad0 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMask.java @@ -0,0 +1,12 @@ +package com.codingapi.springboot.authorization.mask; + +/** + * 列数据脱敏 + */ +public interface ColumnMask { + + boolean support(Object value); + + Object mask(Object value); + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMaskContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMaskContext.java new file mode 100644 index 00000000..5e84d63a --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/ColumnMaskContext.java @@ -0,0 +1,34 @@ +package com.codingapi.springboot.authorization.mask; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class ColumnMaskContext { + + private final List columnMasks; + + private ColumnMaskContext() { + this.columnMasks = new ArrayList<>(); + } + + public void addColumnMask(ColumnMask columnMask) { + this.columnMasks.add(columnMask); + } + + @Getter + private final static ColumnMaskContext instance = new ColumnMaskContext(); + + + public T mask(T value) { + for (ColumnMask columnMask : columnMasks) { + if (columnMask.support(value)) { + return (T)columnMask.mask(value); + } + } + return value; + } + + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/BankCardMask.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/BankCardMask.java new file mode 100644 index 00000000..2aba7e1b --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/BankCardMask.java @@ -0,0 +1,43 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import com.codingapi.springboot.authorization.mask.ColumnMask; + +import java.util.regex.Pattern; + +/** + * 银行卡脱敏 + */ +public class BankCardMask implements ColumnMask { + + private final static Pattern BANK_CARD_MATCHER_PATTERN = Pattern.compile("^\\d{13,19}$"); + private final static Pattern BANK_CARD_MASK_PATTERN = Pattern.compile("(\\d{6})\\d{3,9}(\\d{4})"); + + @Override + public boolean support(Object value) { + if (value instanceof String) { + return BANK_CARD_MATCHER_PATTERN.matcher((String) value).matches(); + } + return false; + } + + @Override + public Object mask(Object value) { + if (value instanceof String) { + String bankCard = (String) value; + int length = bankCard.length(); + // 获取字符串的长度 + int maskLength = length - 10; + + // 手动构造星号部分 + StringBuilder maskedPart = new StringBuilder(maskLength); + for (int i = 0; i < maskLength; i++) { + maskedPart.append("*"); + } + + // 用构造好的星号替换原始字符串中的部分内容 + return BANK_CARD_MASK_PATTERN.matcher((String) value) + .replaceAll("$1" + maskedPart + "$2"); + } + return value; + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/IDCardMask.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/IDCardMask.java new file mode 100644 index 00000000..51541ad9 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/IDCardMask.java @@ -0,0 +1,29 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import com.codingapi.springboot.authorization.mask.ColumnMask; + +import java.util.regex.Pattern; + +/** + * 身份证脱敏 + */ +public class IDCardMask implements ColumnMask { + private final static Pattern ID_CARD_MATCHER_PATTERN = Pattern.compile("^(\\d{15}|\\d{18}|\\d{17}[Xx])$"); + private final static Pattern ID_CARD_MASK_PATTERN = Pattern.compile("(\\d{6})\\d{8}(\\w{4})"); + + @Override + public boolean support(Object value) { + if (value instanceof String) { + return ID_CARD_MATCHER_PATTERN.matcher((String) value).matches(); + } + return false; + } + + @Override + public Object mask(Object value) { + if (value instanceof String) { + return ID_CARD_MASK_PATTERN.matcher( (String) value).replaceAll("$1********$2"); + } + return value; + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/PhoneMask.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/PhoneMask.java new file mode 100644 index 00000000..3903a82f --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/mask/impl/PhoneMask.java @@ -0,0 +1,31 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import com.codingapi.springboot.authorization.mask.ColumnMask; + +import java.util.regex.Pattern; + +/** + * 电话号码脱敏 + */ +public class PhoneMask implements ColumnMask { + + private static final Pattern PHONE_MATCHER_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + private static final Pattern PHONE_MASK_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})"); + + @Override + public boolean support(Object value) { + if (value instanceof String) { + return PHONE_MATCHER_PATTERN.matcher((String) value).matches(); + } + return false; + } + + @Override + public Object mask(Object value) { + if (value instanceof String) { + return PHONE_MASK_PATTERN.matcher((String) value).replaceAll("$1****$2"); + } + return value; + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationProperties.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationProperties.java new file mode 100644 index 00000000..48bfe771 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationProperties.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.authorization.properties; + + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class DataAuthorizationProperties { + + private boolean showSql = false; + + public DataAuthorizationProperties() { + DataAuthorizationPropertyContext.getInstance().setDataAuthorizationProperties(this); + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationPropertyContext.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationPropertyContext.java new file mode 100644 index 00000000..dcb5f582 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/properties/DataAuthorizationPropertyContext.java @@ -0,0 +1,24 @@ +package com.codingapi.springboot.authorization.properties; + +import lombok.Getter; + +public class DataAuthorizationPropertyContext { + + @Getter + private final static DataAuthorizationPropertyContext instance = new DataAuthorizationPropertyContext(); + + private DataAuthorizationPropertyContext(){} + + private DataAuthorizationProperties dataAuthorizationProperties; + + protected void setDataAuthorizationProperties(DataAuthorizationProperties dataAuthorizationProperties){ + this.dataAuthorizationProperties = dataAuthorizationProperties; + } + + public boolean showSql(){ + if(dataAuthorizationProperties!=null) { + return dataAuthorizationProperties.isShowSql(); + } + return false; + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ConditionHandlerRegister.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ConditionHandlerRegister.java new file mode 100644 index 00000000..d39fb34d --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ConditionHandlerRegister.java @@ -0,0 +1,14 @@ +package com.codingapi.springboot.authorization.register; + + +import com.codingapi.springboot.authorization.handler.RowHandler; +import com.codingapi.springboot.authorization.handler.RowHandlerContext; + +public class ConditionHandlerRegister { + + public ConditionHandlerRegister(RowHandler rowHandler) { + if (rowHandler != null) { + RowHandlerContext.getInstance().setRowHandler(rowHandler); + } + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/DataAuthorizationContextRegister.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/DataAuthorizationContextRegister.java new file mode 100644 index 00000000..39ca7096 --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/DataAuthorizationContextRegister.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.authorization.register; + +import com.codingapi.springboot.authorization.DataAuthorizationContext; +import com.codingapi.springboot.authorization.filter.DataAuthorizationFilter; + +import java.util.List; + +public class DataAuthorizationContextRegister { + + public DataAuthorizationContextRegister(List dataAuthorizationFilters) { + if(dataAuthorizationFilters!=null) { + for (DataAuthorizationFilter filter : dataAuthorizationFilters) { + DataAuthorizationContext.getInstance().addDataAuthorizationFilter(filter); + } + } + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ResultSetHandlerRegister.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ResultSetHandlerRegister.java new file mode 100644 index 00000000..6070269a --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/ResultSetHandlerRegister.java @@ -0,0 +1,15 @@ +package com.codingapi.springboot.authorization.register; + + +import com.codingapi.springboot.authorization.handler.ColumnHandler; +import com.codingapi.springboot.authorization.handler.ColumnHandlerContext; + +public class ResultSetHandlerRegister { + + public ResultSetHandlerRegister(ColumnHandler columnHandler){ + if(columnHandler !=null) { + ColumnHandlerContext.getInstance().setColumnHandler(columnHandler); + } + } + +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/SQLInterceptorRegister.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/SQLInterceptorRegister.java new file mode 100644 index 00000000..57534f0d --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/register/SQLInterceptorRegister.java @@ -0,0 +1,14 @@ +package com.codingapi.springboot.authorization.register; + + +import com.codingapi.springboot.authorization.interceptor.SQLInterceptor; +import com.codingapi.springboot.authorization.interceptor.SQLInterceptorContext; + +public class SQLInterceptorRegister { + + public SQLInterceptorRegister(SQLInterceptor sqlInterceptor) { + if(sqlInterceptor!=null) { + SQLInterceptorContext.getInstance().setSqlInterceptor(sqlInterceptor); + } + } +} diff --git a/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/utils/SQLUtils.java b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/utils/SQLUtils.java new file mode 100644 index 00000000..817da94c --- /dev/null +++ b/springboot-starter-data-authorization/src/main/java/com/codingapi/springboot/authorization/utils/SQLUtils.java @@ -0,0 +1,25 @@ +package com.codingapi.springboot.authorization.utils; + +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Select; + +public class SQLUtils { + + /** + * 判断是否为查询 + */ + public static boolean isQuerySql(String sql) { + if (sql == null || sql.trim().isEmpty()) { + return false; // 空字符串或 null 不是有效 SQL + } + try { + Statement statement = CCJSqlParserUtil.parse(sql); + return statement instanceof Select; + } catch (Exception e) { + return false; + } + } + + +} diff --git a/springboot-starter-data-authorization/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-data-authorization/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..77aba71d --- /dev/null +++ b/springboot-starter-data-authorization/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.codingapi.springboot.authorization.DataAuthorizationConfiguration diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationContextTest.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationContextTest.java new file mode 100644 index 00000000..3dd25040 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationContextTest.java @@ -0,0 +1,378 @@ +package com.codingapi.springboot.authorization; + +import com.codingapi.springboot.authorization.current.CurrentUser; +import com.codingapi.springboot.authorization.enhancer.DataPermissionSQLEnhancer; +import com.codingapi.springboot.authorization.entity.Depart; +import com.codingapi.springboot.authorization.entity.Unit; +import com.codingapi.springboot.authorization.entity.User; +import com.codingapi.springboot.authorization.filter.DefaultDataAuthorizationFilter; +import com.codingapi.springboot.authorization.handler.Condition; +import com.codingapi.springboot.authorization.handler.RowHandler; +import com.codingapi.springboot.authorization.interceptor.SQLRunningContext; +import com.codingapi.springboot.authorization.mask.ColumnMaskContext; +import com.codingapi.springboot.authorization.mask.impl.BankCardMask; +import com.codingapi.springboot.authorization.mask.impl.IDCardMask; +import com.codingapi.springboot.authorization.mask.impl.PhoneMask; +import com.codingapi.springboot.authorization.repository.DepartRepository; +import com.codingapi.springboot.authorization.repository.UnitRepository; +import com.codingapi.springboot.authorization.repository.UserRepository; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.SelectItemVisitor; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.annotation.Rollback; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Rollback(value = false) +public class DataAuthorizationContextTest { + + @Autowired + private UserRepository userRepository; + @Autowired + private DepartRepository departRepository; + @Autowired + private UnitRepository unitRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + @Order(1) + void test1() { + + unitRepository.deleteAll(); + departRepository.deleteAll(); + userRepository.deleteAll(); + + + DataAuthorizationContext.getInstance().clearDataAuthorizationFilters(); + + DataAuthorizationContext.getInstance().addDataAuthorizationFilter(new DefaultDataAuthorizationFilter() { + + @Override + public boolean supportRowAuthorization(String tableName, String tableAlias) { + User user = CurrentUser.getInstance().getUser(); + // 模拟仅当用户为lorne时,才进行行级过滤 + return user.getName().equalsIgnoreCase("bob"); + } + + @Override + public Condition rowAuthorization(String tableName, String tableAlias) { + if (tableName.equalsIgnoreCase("t_unit")) { + long unitId = CurrentUser.getInstance().getUser().getUnitId(); + String conditionTemplate = "%s.id = " + unitId; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + if (tableName.equalsIgnoreCase("t_depart")) { + long departId = CurrentUser.getInstance().getUser().getDepartId(); + String conditionTemplate = "%s.id = " + departId; + + // 在条件处理的过程中,执行的查询都将不会被拦截 + List departs = departRepository.findAll(); + log.info("departs:{}", departs); + assertEquals(2, departs.size()); + + return Condition.formatCondition(conditionTemplate, tableAlias); + } + if (tableName.equalsIgnoreCase("t_user")) { + long departId = CurrentUser.getInstance().getUser().getDepartId(); + String conditionTemplate = "%s.depart_id = " + departId; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + } + + }); + + Unit rootUnit = new Unit("Coding总公司"); + unitRepository.save(rootUnit); + + Unit sdUnit = new Unit("Coding山东分公司", rootUnit.getId()); + unitRepository.save(sdUnit); + + Depart jgbDepart = new Depart("Coding架构部", rootUnit.getId()); + departRepository.save(jgbDepart); + + Depart xmbDepart = new Depart("Coding项目部", sdUnit.getId()); + departRepository.save(xmbDepart); + + + User lorne = new User("lorne", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", jgbDepart); + User bob = new User("bob", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + User tom = new User("tom", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + + userRepository.save(lorne); + userRepository.save(bob); + userRepository.save(tom); + + + CurrentUser.getInstance().setUser(bob); + + + PageRequest request = PageRequest.of(0, 100); + Page users = userRepository.findAll(request); + + + System.out.println(users.getTotalElements()); + users.forEach(System.out::println); + + + assertEquals(2, users.getTotalElements()); + assertEquals(2, userRepository.count()); + assertEquals(1, departRepository.count()); + assertEquals(1, unitRepository.count()); + + } + + + @Test + @Order(2) + void test2() { + + unitRepository.deleteAll(); + departRepository.deleteAll(); + userRepository.deleteAll(); + + + ColumnMaskContext.getInstance().addColumnMask(new IDCardMask()); + ColumnMaskContext.getInstance().addColumnMask(new PhoneMask()); + ColumnMaskContext.getInstance().addColumnMask(new BankCardMask()); + + DataAuthorizationContext.getInstance().clearDataAuthorizationFilters(); + + DataAuthorizationContext.getInstance().addDataAuthorizationFilter(new DefaultDataAuthorizationFilter() { + @Override + public T columnAuthorization(String tableName, String columnName, T value) { + return ColumnMaskContext.getInstance().mask(value); + } + + @Override + public boolean supportColumnAuthorization(String tableName, String columnName, Object value) { + User user = CurrentUser.getInstance().getUser(); + return user != null && user.getName().equalsIgnoreCase("bob"); + } + + }); + + Unit rootUnit = new Unit("Coding总公司"); + unitRepository.save(rootUnit); + + Unit sdUnit = new Unit("Coding山东分公司", rootUnit.getId()); + unitRepository.save(sdUnit); + + Depart jgbDepart = new Depart("Coding架构部", rootUnit.getId()); + departRepository.save(jgbDepart); + + Depart xmbDepart = new Depart("Coding项目部", sdUnit.getId()); + departRepository.save(xmbDepart); + + + User lorne = new User("lorne", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", jgbDepart); + User bob = new User("bob", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + User tom = new User("tom", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + + userRepository.save(lorne); + userRepository.save(bob); + userRepository.save(tom); + + assertTrue(SQLRunningContext.getInstance().skipDataAuthorization(() -> userRepository.findAll()).size() >= 3); + + CurrentUser.getInstance().setUser(bob); + + PageRequest request = PageRequest.of(0, 100); + Page users = userRepository.findAll(request); + assertTrue(users.getTotalElements() >= 3); + + for (User user : users) { + assertEquals("138****5678", user.getPhone()); + } + + CurrentUser.getInstance().setUser(lorne); + + users = userRepository.findAll(request); + assertTrue(users.getTotalElements() >= 3); + + for (User user : users) { + assertEquals("13812345678", user.getPhone()); + } + + + } + + + @Test + @Order(3) + void test3() { + + unitRepository.deleteAll(); + departRepository.deleteAll(); + userRepository.deleteAll(); + + ColumnMaskContext.getInstance().addColumnMask(new IDCardMask()); + ColumnMaskContext.getInstance().addColumnMask(new PhoneMask()); + ColumnMaskContext.getInstance().addColumnMask(new BankCardMask()); + + DataAuthorizationContext.getInstance().clearDataAuthorizationFilters(); + + DataAuthorizationContext.getInstance().addDataAuthorizationFilter(new DefaultDataAuthorizationFilter() { + @Override + public T columnAuthorization(String tableName, String columnName, T value) { + return ColumnMaskContext.getInstance().mask(value); + } + + @Override + public boolean supportColumnAuthorization(String tableName, String columnName, Object value) { + return true; + } + + }); + + Unit rootUnit = new Unit("Coding总公司"); + unitRepository.save(rootUnit); + + Unit sdUnit = new Unit("Coding山东分公司", rootUnit.getId()); + unitRepository.save(sdUnit); + + Depart jgbDepart = new Depart("Coding架构部", rootUnit.getId()); + departRepository.save(jgbDepart); + + Depart xmbDepart = new Depart("Coding项目部", sdUnit.getId()); + departRepository.save(xmbDepart); + + User lorne = new User("lorne", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", jgbDepart); + User bob = new User("bob", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + User tom = new User("tom", LocalDate.parse("1991-01-01"), "beijing", "110105199003078999", "13812345678", xmbDepart); + + userRepository.save(lorne); + userRepository.save(bob); + userRepository.save(tom); + + List> users = jdbcTemplate.queryForList("select * from t_user"); + System.out.println(users); + assertEquals(3, users.size()); + + for (Map user : users) { + assertEquals("138****5678", user.get("phone")); + } + + } + + +// @Test + @Order(4) + void test4() throws Exception{ + String sql = "SELECT\n" + + "\tt.* \n" + + "FROM\n" + + "\t(\n" + + "\t\tSELECT\n" + + "\t\t\tUNYiV.id AS '历史工作经历编号',\n" + + "\t\t\tUNYiV.company_name AS '历史工作单位',\n" + + "\t\t\tUNYiV.depart_name AS '历史工作部门',\n" + + "\t\t\tUNYiV.post_name AS '历史工作岗位',\n" + + "\t\t\tUNYiV.start_date AS '开始时间',\n" + + "\t\t\tUNYiV.end_date AS '结束时间',\n" + + "\t\t\towasH.员工编号 AS '员工编号',\n" + + "\t\t\towasH.员工姓名 AS '员工姓名',\n" + + "\t\t\towasH.员工生日 AS '员工生日',\n" + + "\t\t\towasH.员工地址 AS '员工地址',\n" + + "\t\t\towasH.身份证号码 AS '身份证号码',\n" + + "\t\t\towasH.手机号 AS '手机号',\n" + + "\t\t\towasH.部门编号 AS '部门编号',\n" + + "\t\t\towasH.岗位编号 AS '岗位编号',\n" + + "\t\t\towasH.任现职编号 AS '任现职编号',\n" + + "\t\t\towasH.社团编号 AS '社团编号',\n" + + "\t\t\towasH.社团名称 AS '社团名称',\n" + + "\t\t\towasH.创建时间 AS '创建时间' \n" + + "\t\tFROM\n" + + "\t\t\tt_work AS pehMS,\n" + + "\t\t\tt_employee AS OGwG7,\n" + + "\t\t\tt_work_history AS UNYiV,\n" + + "\t\t\t(\n" + + "\t\t\t\tSELECT\n" + + "\t\t\t\t\tWXJj8.id AS '员工编号',\n" + + "\t\t\t\t\tWXJj8.NAME AS '员工姓名',\n" + + "\t\t\t\t\tWXJj8.birth_date AS '员工生日',\n" + + "\t\t\t\t\tWXJj8.address AS '员工地址',\n" + + "\t\t\t\t\tWXJj8.id_card AS '身份证号码',\n" + + "\t\t\t\t\tWXJj8.phone AS '手机号',\n" + + "\t\t\t\t\tWXJj8.depart_id AS '部门编号',\n" + + "\t\t\t\t\tWXJj8.post_id AS '岗位编号',\n" + + "\t\t\t\t\tWXJj8.work_id AS '任现职编号',\n" + + "\t\t\t\t\trnGD4.id AS '社团编号',\n" + + "\t\t\t\t\trnGD4.NAME AS '社团名称',\n" + + "\t\t\t\t\trnGD4.create_date AS '创建时间' \n" + + "\t\t\t\tFROM\n" + + "\t\t\t\t\tt_employee AS WXJj8,\n" + + "\t\t\t\t\tt_league_employee AS dEj96,\n" + + "\t\t\t\t\tt_league AS rnGD4 \n" + + "\t\t\t\tWHERE\n" + + "\t\t\t\t\trnGD4.id < 100 \n" + + "\t\t\t\t\tAND dEj96.employee_id = WXJj8.id \n" + + "\t\t\t\t\tAND dEj96.league_id = rnGD4.id \n" + + "\t\t\t\t\tAND 1 = 1 \n" + + "\t\t\t) AS owasH \n" + + "\t\tWHERE\n" + + "\t\t\tUNYiV.employee_id = OGwG7.id \n" + + "\t\t\tAND OGwG7.work_id = pehMS.id \n" + + "\t\t\tAND owasH.任现职编号 = pehMS.id \n" + + "\t\t\tAND 1 = 1 \n" + + "\t) AS t , t_employee AS e where t.员工编号 = e.id and e.id = 1"; + + + DataAuthorizationContext.getInstance().clearDataAuthorizationFilters(); + DataAuthorizationContext.getInstance().addDataAuthorizationFilter(new DefaultDataAuthorizationFilter() { + @Override + public Condition rowAuthorization(String tableName, String tableAlias) { + return super.rowAuthorization(tableName, tableAlias); + } + + @Override + public T columnAuthorization(String tableName, String columnName, T value) { + System.out.println("tableName:" + tableName + ",columnName:" + columnName + ",value:" + value); + return value; + } + + @Override + public boolean supportColumnAuthorization(String tableName, String columnName, Object value) { + return true; + } + + @Override + public boolean supportRowAuthorization(String tableName, String tableAlias) { + return true; + } + }); + + + List> data = jdbcTemplate.queryForList(sql); +// System.out.println(data); + } + + + + +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationTestApplication.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationTestApplication.java new file mode 100644 index 00000000..0760ec6a --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/DataAuthorizationTestApplication.java @@ -0,0 +1,13 @@ +package com.codingapi.springboot.authorization; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DataAuthorizationTestApplication { + + public static void main(String[] args) { + SpringApplication.run(DataAuthorizationConfiguration.class,args); + } + +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/analyzer/SelectSQLAnalyzerTest.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/analyzer/SelectSQLAnalyzerTest.java new file mode 100644 index 00000000..008062a5 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/analyzer/SelectSQLAnalyzerTest.java @@ -0,0 +1,225 @@ +package com.codingapi.springboot.authorization.analyzer; + +import com.codingapi.springboot.authorization.enhancer.DataPermissionSQLEnhancer; +import com.codingapi.springboot.authorization.handler.Condition; +import com.codingapi.springboot.authorization.handler.RowHandler; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.*; + +class SelectSQLAnalyzerTest { + + + @Test + void test1() throws SQLException { + String sql = "select t1.*,t2.* from (SELECT * FROM t_employee as a2 WHERE id = 100 ) t1 ," + + " (SELECT * FROM t_employee as a1 ) t2 ," + + " (select * from t_employee a3 left join t_unit u on a3.unit_id = u.id ) t3 ," + + " (select 1 =1 ) as t4 " + + " limit 100"; + + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_employee")) { + String conditionTemplate = "%s.id > 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + String newSql = builder.getNewSQL(); + System.out.println(newSql); + //SELECT t1.*, t2.* FROM (SELECT * FROM t_employee AS a2 WHERE a2.id > 100 AND id = 100) t1, + // (SELECT * FROM t_employee AS a1 WHERE a1.id > 100) t2, + // (SELECT * FROM t_employee a3 LEFT JOIN t_unit u ON a3.unit_id = u.id WHERE a3.id > 100) t3, + // (SELECT 1 = 1) AS t4 LIMIT 100 + assertEquals( + "SELECT t1.*, t2.* FROM (SELECT * FROM t_employee AS a2 WHERE a2.id > 100 AND id = 100) t1," + + " (SELECT * FROM t_employee AS a1 WHERE a1.id > 100) t2, " + + "(SELECT * FROM t_employee a3 LEFT JOIN t_unit u ON a3.unit_id = u.id WHERE a3.id > 100) t3, " + + "(SELECT 1 = 1) AS t4 LIMIT 100", newSql); + } + + @Test + void test2() throws SQLException { + String sql = "select e1_0.id,e1_0.address,e1_0.birth_date,e1_0.depart_id,e1_0.id_card,e1_0.name,e1_0.phone,e1_0.post_id,e1_0.work_id from t_employee e1_0 limit ?,?"; + + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_employee")) { + String conditionTemplate = "%s.id > 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + String newSql = builder.getNewSQL(); + System.out.println(newSql); + assertEquals("SELECT e1_0.id, e1_0.address, e1_0.birth_date, e1_0.depart_id, e1_0.id_card, e1_0.name, e1_0.phone, e1_0.post_id, e1_0.work_id FROM t_employee e1_0 WHERE e1_0.id > 100 LIMIT ?, ?", newSql); } + + + @Test + void test3() throws SQLException { + String sql = "select aue1_0.ba_org_code from ba03_administrative_unit aue1_0 where aue1_0.ba_org_code like (?||'__') order by aue1_0.ba_org_code desc"; + + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("ba03_administrative_unit")) { + String conditionTemplate = "%s.id > 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + String newSql = builder.getNewSQL(); + System.out.println(newSql); + assertEquals("SELECT aue1_0.ba_org_code FROM ba03_administrative_unit aue1_0 WHERE aue1_0.id > 100 AND aue1_0.ba_org_code LIKE (? || '__') ORDER BY aue1_0.ba_org_code DESC", newSql); + } + + @Test + void test4() throws SQLException{ + String sql = "SELECT\n" + + "\tUNYiV.id AS '历史工作经历编号',\n" + + "\tUNYiV.company_name AS '历史工作单位',\n" + + "\tUNYiV.depart_name AS '历史工作部门',\n" + + "\tUNYiV.post_name AS '历史工作岗位',\n" + + "\tUNYiV.start_date AS '开始时间',\n" + + "\tUNYiV.end_date AS '结束时间',\n" + + "\towasH.员工编号 AS '员工编号',\n" + + "\towasH.员工姓名 AS '员工姓名',\n" + + "\towasH.员工生日 AS '员工生日',\n" + + "\towasH.员工地址 AS '员工地址',\n" + + "\towasH.身份证号码 AS '身份证号码',\n" + + "\towasH.手机号 AS '手机号',\n" + + "\towasH.部门编号 AS '部门编号',\n" + + "\towasH.岗位编号 AS '岗位编号',\n" + + "\towasH.任现职编号 AS '任现职编号',\n" + + "\towasH.社团编号 AS '社团编号',\n" + + "\towasH.社团名称 AS '社团名称',\n" + + "\towasH.创建时间 AS '创建时间' \n" + + "FROM\n" + + "\tt_work AS pehMS,\n" + + "\tt_employee AS OGwG7,\n" + + "\tt_work_history AS UNYiV,\n" + + "\t(\n" + + "\t\tSELECT\n" + + "\t\t\tWXJj8.id AS '员工编号',\n" + + "\t\t\tWXJj8.NAME AS '员工姓名',\n" + + "\t\t\tWXJj8.birth_date AS '员工生日',\n" + + "\t\t\tWXJj8.address AS '员工地址',\n" + + "\t\t\tWXJj8.id_card AS '身份证号码',\n" + + "\t\t\tWXJj8.phone AS '手机号',\n" + + "\t\t\tWXJj8.depart_id AS '部门编号',\n" + + "\t\t\tWXJj8.post_id AS '岗位编号',\n" + + "\t\t\tWXJj8.work_id AS '任现职编号',\n" + + "\t\t\trnGD4.id AS '社团编号',\n" + + "\t\t\trnGD4.NAME AS '社团名称',\n" + + "\t\t\trnGD4.create_date AS '创建时间' \n" + + "\t\tFROM\n" + + "\t\t\tt_employee AS WXJj8,\n" + + "\t\t\tt_league_employee AS dEj96,\n" + + "\t\t\tt_league AS rnGD4 \n" + + "\t\tWHERE\n" + + "\t\t\tdEj96.employee_id = WXJj8.id \n" + + "\t\t\tAND dEj96.league_id = rnGD4.id \n" + + "\t\t\tAND 1 = 1 \n" + + "\t) AS owasH \n" + + "WHERE\n" + + "\tUNYiV.employee_id = OGwG7.id \n" + + "\tAND OGwG7.work_id = pehMS.id \n" + + "\tAND owasH.任现职编号 = pehMS.id \n" + + "\tAND 1 = 1"; + + + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_league")) { + String conditionTemplate = "%s.id < 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + System.out.println(builder.getNewSQL()); + System.out.println(builder.getTableAlias());; + } + + @Test + @Order(5) + void test5() throws Exception{ + String sql = "SELECT next_val AS id_val FROM t_league_seq FOR UPDATE"; + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_league")) { + String conditionTemplate = "%s.id < 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + System.out.println(builder.getNewSQL()); + System.out.println(builder.getTableAlias());; + } + + + @Test + @Order(6) + void test6() throws Exception{ + String sql = "SELECT 1=1"; + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_league")) { + String conditionTemplate = "%s.id < 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + System.out.println(builder.getNewSQL()); + System.out.println(builder.getTableAlias());; + } + + @Test + @Order(7) + void test7() throws Exception{ + String sql = "SELECT * from t_employee"; + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("t_employee")) { + String conditionTemplate = "%s.id < 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + System.out.println(builder.getNewSQL()); + System.out.println(builder.getTableAlias());; + } + + + @Test + @Order(8) + void test8() throws Exception{ + String sql = "select ade1_0.id,ade1_0.ba_dept_name,ade1_0.ba_org_shortname,ade1_0.ba_dept_code,ade1_0.ba_code,ade1_0.ba_dept_property_code,ade1_0.ba_parent_type,ade1_0.ba_real_super_org_id,ade1_0.ba_org_is_avoidance_dept,ade1_0.ba_real_super_org_id,ade1_0.ba_super_org_name " + + "from ba04_administrative_department ade1_0 where ade1_0.ba_parent_type=0" + + " union all select ade2_0.id,ade2_0.ba_dept_name,ade2_0.ba_org_shortname,ade2_0.ba_dept_code,ade2_0.ba_code,ade2_0.ba_dept_property_code,ade2_0.ba_parent_type,ade2_0.ba_real_super_org_id,ade2_0.ba_org_is_avoidance_dept,ade3_0.ba_real_super_org_id,ade3_0.ba_super_org_name" + + " from ba04_administrative_department ade2_0 left join ba04_administrative_department ade3_0 on ade2_0.ba_real_super_org_id=ade3_0.id " + + "where ade2_0.ba_real_super_org_id=1"; + + + + RowHandler rowHandler = (subSql, tableName, tableAlias) -> { + if (tableName.equalsIgnoreCase("ba04_administrative_department")) { + String conditionTemplate = "%s.id < 100 "; + return Condition.formatCondition(conditionTemplate, tableAlias); + } + return null; + }; + DataPermissionSQLEnhancer builder = new DataPermissionSQLEnhancer(sql, rowHandler); + String newSQL = builder.getNewSQL(); + System.out.println(newSQL); + System.out.println(builder.getTableAlias()); + assertEquals("SELECT ade1_0.id, ade1_0.ba_dept_name, ade1_0.ba_org_shortname, ade1_0.ba_dept_code, ade1_0.ba_code, ade1_0.ba_dept_property_code, ade1_0.ba_parent_type, ade1_0.ba_real_super_org_id, ade1_0.ba_org_is_avoidance_dept, ade1_0.ba_real_super_org_id, ade1_0.ba_super_org_name FROM ba04_administrative_department ade1_0 WHERE ade1_0.id < 100 AND ade1_0.ba_parent_type = 0 " + + "UNION ALL SELECT ade2_0.id, ade2_0.ba_dept_name, ade2_0.ba_org_shortname, ade2_0.ba_dept_code, ade2_0.ba_code, ade2_0.ba_dept_property_code, ade2_0.ba_parent_type, ade2_0.ba_real_super_org_id, ade2_0.ba_org_is_avoidance_dept, ade3_0.ba_real_super_org_id, ade3_0.ba_super_org_name FROM ba04_administrative_department ade2_0 LEFT JOIN ba04_administrative_department ade3_0 ON ade2_0.ba_real_super_org_id = ade3_0.id WHERE ade3_0.id < 100 AND ade2_0.id < 100 AND ade2_0.ba_real_super_org_id = 1", newSQL); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/current/CurrentUser.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/current/CurrentUser.java new file mode 100644 index 00000000..186c3d00 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/current/CurrentUser.java @@ -0,0 +1,27 @@ +package com.codingapi.springboot.authorization.current; + +import com.codingapi.springboot.authorization.entity.User; +import lombok.Getter; + +public class CurrentUser { + + @Getter + private final static CurrentUser instance = new CurrentUser(); + + private final ThreadLocal threadLocal = new ThreadLocal<>(); + + private CurrentUser(){ + } + + public void setUser(User user){ + threadLocal.set(user); + } + + public User getUser(){ + return threadLocal.get(); + } + + public void remove(){ + threadLocal.remove(); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Depart.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Depart.java new file mode 100644 index 00000000..865d8801 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Depart.java @@ -0,0 +1,38 @@ +package com.codingapi.springboot.authorization.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Setter +@Getter +@Entity +@Table(name = "t_depart") +@NoArgsConstructor +public class Depart { + + @Id + @GeneratedValue + private long id; + + private String name; + + private long parentId; + + private long unitId; + + public Depart(String name, long unitId,long parentId) { + this.name = name; + this.parentId = parentId; + this.unitId = unitId; + } + + public Depart(String name,long unitId) { + this(name,unitId,0); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Unit.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Unit.java new file mode 100644 index 00000000..4ea4b0a7 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/Unit.java @@ -0,0 +1,34 @@ +package com.codingapi.springboot.authorization.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@Entity +@Table(name = "t_unit") +@NoArgsConstructor +public class Unit { + + @Id + @GeneratedValue + private long id; + + private String name; + + private long parentId; + + public Unit(String name, long parentId) { + this.name = name; + this.parentId = parentId; + } + + public Unit(String name) { + this(name,0); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/User.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/User.java new file mode 100644 index 00000000..04fca1f3 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/entity/User.java @@ -0,0 +1,49 @@ +package com.codingapi.springboot.authorization.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.time.LocalDate; + +@Setter +@Getter +@Entity +@Table(name = "t_user") +@NoArgsConstructor +@ToString +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private LocalDate birthDate; + + private String address; + + private String idCard; + + private String phone; + + private long unitId; + + private long departId; + + public User(String name, LocalDate birthDate, String address, String idCard, String phone,Depart depart) { + this.name = name; + this.birthDate = birthDate; + this.address = address; + this.idCard = idCard; + this.phone = phone; + this.unitId = depart.getUnitId(); + this.departId = depart.getId(); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/BankCardMaskTest.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/BankCardMaskTest.java new file mode 100644 index 00000000..0b75e2e4 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/BankCardMaskTest.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BankCardMaskTest { + + @Test + void test() { + String bankCard = "6222021001111111111"; + BankCardMask mask = new BankCardMask(); + assertTrue(mask.support(bankCard)); + assertEquals("622202*********1111", mask.mask(bankCard)); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/IDCardMaskTest.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/IDCardMaskTest.java new file mode 100644 index 00000000..3e758452 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/IDCardMaskTest.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class IDCardMaskTest { + + @Test + void test(){ + String idCard = "110101199003074012"; + IDCardMask mask = new IDCardMask(); + assertTrue(mask.support(idCard)); + assertEquals("110101********4012", mask.mask(idCard)); + } +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/PhoneMaskTest.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/PhoneMaskTest.java new file mode 100644 index 00000000..d5c8d4f5 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/mask/impl/PhoneMaskTest.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.authorization.mask.impl; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class PhoneMaskTest { + + @Test + void test(){ + String phone = "15562581234"; + PhoneMask phoneMask = new PhoneMask(); + assertTrue(phoneMask.support(phone)); + assertEquals("155****1234", phoneMask.mask(phone)); + } + +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/DepartRepository.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/DepartRepository.java new file mode 100644 index 00000000..b9c00b84 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/DepartRepository.java @@ -0,0 +1,8 @@ +package com.codingapi.springboot.authorization.repository; + +import com.codingapi.springboot.authorization.entity.Depart; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DepartRepository extends JpaRepository { + +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UnitRepository.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UnitRepository.java new file mode 100644 index 00000000..0bc6416d --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UnitRepository.java @@ -0,0 +1,8 @@ +package com.codingapi.springboot.authorization.repository; + +import com.codingapi.springboot.authorization.entity.Unit; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UnitRepository extends JpaRepository { + +} diff --git a/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UserRepository.java b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UserRepository.java new file mode 100644 index 00000000..f36df247 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/java/com/codingapi/springboot/authorization/repository/UserRepository.java @@ -0,0 +1,12 @@ +package com.codingapi.springboot.authorization.repository; + +import com.codingapi.springboot.authorization.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserRepository extends JpaRepository { + + + List findUserByDepartId(long departId); +} diff --git a/springboot-starter-data-authorization/src/test/resources/application.properties b/springboot-starter-data-authorization/src/test/resources/application.properties new file mode 100644 index 00000000..4e944839 --- /dev/null +++ b/springboot-starter-data-authorization/src/test/resources/application.properties @@ -0,0 +1,13 @@ + +spring.datasource.driver-class-name=com.codingapi.springboot.authorization.jdbc.AuthorizationJdbcDriver +spring.datasource.url=jdbc:h2:file:./test.db +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true + +#spring.datasource.driver-class-name=com.codingapi.springboot.authorization.jdbc.AuthorizationJdbcDriver +#spring.datasource.url=jdbc:mysql://localhost:3306/example?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true +#spring.datasource.username=root +#spring.datasource.password=lorne4j#2024 + +logging.level.com.codingapi.springboot.authorization=debug diff --git a/springboot-starter-data-fast/pom.xml b/springboot-starter-data-fast/pom.xml index 141c3862..f7e84726 100644 --- a/springboot-starter-data-fast/pom.xml +++ b/springboot-starter-data-fast/pom.xml @@ -5,14 +5,14 @@ springboot-parent com.codingapi.springboot - 2.1.11 + 2.10.5 4.0.0 springboot-starter-data-fast - 17 + 8 @@ -28,6 +28,12 @@ springboot-starter + + jakarta.servlet + jakarta.servlet-api + provided + + org.springframework.boot spring-boot-starter-data-jpa @@ -39,6 +45,26 @@ test + + org.apache.commons + commons-text + + + + org.apache.groovy + groovy + + + + org.apache.groovy + groovy-json + + + + org.apache.groovy + groovy-xml + + - \ No newline at end of file + diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/DataFastConfiguration.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/DataFastConfiguration.java index 0dcf4268..33136595 100644 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/DataFastConfiguration.java +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/DataFastConfiguration.java @@ -1,10 +1,9 @@ package com.codingapi.springboot.fast; -import com.codingapi.springboot.fast.executor.JpaExecutor; import com.codingapi.springboot.fast.manager.EntityManagerInitializer; -import com.codingapi.springboot.fast.mapping.MvcEndpointMapping; -import com.codingapi.springboot.fast.registrar.MvcMappingRegistrar; -import org.springframework.aop.Advisor; +import com.codingapi.springboot.fast.mapping.FastMvcMappingRegister; +import com.codingapi.springboot.fast.script.FastScriptMappingRegister; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -12,8 +11,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import jakarta.persistence.EntityManager; -import java.util.List; +import javax.persistence.EntityManager; @Configuration @ConditionalOnClass(WebMvcConfigurer.class) @@ -22,28 +20,21 @@ public class DataFastConfiguration { @Bean @ConditionalOnMissingBean - public MvcEndpointMapping mvcEndpointMapping(RequestMappingHandlerMapping handlerMapping) { - return new MvcEndpointMapping(handlerMapping); + public FastMvcMappingRegister mvcMappingRegister(@Qualifier("requestMappingHandlerMapping") RequestMappingHandlerMapping handlerMapping) { + return new FastMvcMappingRegister(handlerMapping); } - @Bean(initMethod = "registerMvcMapping") - @ConditionalOnMissingBean - public MvcMappingRegistrar mappingRegistrar(MvcEndpointMapping mvcEndpointMapping, - JpaExecutor jpaExecutor, - List advisors) { - return new MvcMappingRegistrar(mvcEndpointMapping, jpaExecutor,advisors); - } @Bean @ConditionalOnMissingBean - public EntityManagerInitializer entityManagerInitializer(EntityManager entityManager){ + public EntityManagerInitializer entityManagerInitializer(EntityManager entityManager) { return new EntityManagerInitializer(entityManager); } + @Bean - @ConditionalOnMissingBean - public JpaExecutor jpaExecutor(EntityManager entityManager) { - return new JpaExecutor(entityManager); + public FastScriptMappingRegister scriptMappingRegister(FastMvcMappingRegister fastMvcMappingRegister) { + return new FastScriptMappingRegister(fastMvcMappingRegister); } } diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastController.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastController.java deleted file mode 100644 index e566cf7b..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastController.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.codingapi.springboot.fast.annotation; - -import java.lang.annotation.*; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface FastController { - - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastMapping.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastMapping.java deleted file mode 100644 index 1b1d8bb2..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/annotation/FastMapping.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.codingapi.springboot.fast.annotation; - - -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@ResponseBody -public @interface FastMapping { - - /** - * execute jpa hql - */ - String value() default ""; - - - /** - * execute jpa count hql - */ - String countQuery() default ""; - - /** - * mvc request method - */ - RequestMethod method() default RequestMethod.GET; - - /** - * mvc request url - */ - String mapping() default ""; - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/exception/FastMappingErrorException.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/exception/FastMappingErrorException.java deleted file mode 100644 index ce41c99c..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/exception/FastMappingErrorException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.codingapi.springboot.fast.exception; - -public class FastMappingErrorException extends Exception { - - public FastMappingErrorException(String message) { - super(message); - } -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaExecutor.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaExecutor.java deleted file mode 100644 index 04067091..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaExecutor.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.codingapi.springboot.fast.executor; - -import com.codingapi.springboot.framework.dto.response.MultiResponse; -import com.codingapi.springboot.framework.dto.response.SingleResponse; -import lombok.AllArgsConstructor; -import org.springframework.data.domain.Page; - -import jakarta.persistence.EntityManager; -import java.util.Collection; -import java.util.List; - -@AllArgsConstructor -public class JpaExecutor { - - private final EntityManager entityManager; - - public Object execute(String hql, String countHql, Object[] args, Class returnType) { - //only execute query sql - JpaQuery query = new JpaQuery(hql, countHql, args, entityManager); - - if (returnType.equals(SingleResponse.class)) { - List list = (List) query.getResultList(); - return list == null && list.size() > 0 ? SingleResponse.of(list.get(0)) : SingleResponse.empty(); - } - - if (returnType.equals(MultiResponse.class)) { - Object returnData = query.getResultList(); - if (Page.class.isAssignableFrom(returnData.getClass())) { - return MultiResponse.of((Page) returnData); - } - - if (Collection.class.isAssignableFrom(returnData.getClass())) { - return MultiResponse.of((Collection) returnData); - } - } - - return query.getResultList(); - } -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaQuery.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaQuery.java deleted file mode 100644 index dbc4e118..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/JpaQuery.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.codingapi.springboot.fast.executor; - -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.Parameter; -import jakarta.persistence.Query; -import java.lang.reflect.Field; -import java.util.Set; - -@Slf4j -public class JpaQuery { - private final Object[] args; - private final Query query; - private final String hql; - private final String countHql; - - private final EntityManager entityManager; - - public JpaQuery(String hql, String countHql, Object[] args, EntityManager entityManager) { - this.hql = hql; - this.countHql = countHql; - this.args = args; - this.entityManager = entityManager; - this.query = entityManager.createQuery(hql); - this.initParameter(query); - } - - /** - * init query parameter - */ - @SneakyThrows - private void initParameter(Query query) { - if (args != null && args.length > 0) { - Set> parameters = query.getParameters(); - for (Parameter parameter : parameters) { - Integer position = parameter.getPosition(); - if (position != null) { - query.setParameter(position, args[position - 1]); - } - if (StringUtils.hasText(parameter.getName())) { - String name = parameter.getName(); - Object obj = args[0]; - Field field = ReflectionUtils.findField(obj.getClass(), name); - if (field != null) { - field.setAccessible(true); - query.setParameter(name, field.get(obj)); - } - } - } - } - } - - /** - * is Page Request - */ - private boolean isPageable() { - if (args != null && args.length > 0 && StringUtils.hasText(countHql)) { - Object lastObj = args[args.length - 1]; - return lastObj instanceof Pageable; - } - return false; - } - - /** - * get PageRequest - */ - private Pageable getPageable() { - if (isPageable()) { - Object lastObj = args[args.length - 1]; - return (Pageable) lastObj; - } - return null; - } - - /** - * execute get list result data - */ - public Object getResultList() { - if (isPageable()) { - Pageable pageable = getPageable(); - query.setFirstResult((int) pageable.getOffset()); - query.setMaxResults(pageable.getPageSize()); - long total = getCount(); - return new PageImpl<>(query.getResultList(), pageable, total); - } - return query.getResultList(); - } - - - /** - * get sql execute data count - */ - private long getCount() { - Query countQuery = entityManager.createQuery(countHql); - initParameter(countQuery); - return (Long) countQuery.getSingleResult(); - } - - - /** - * get single result data - */ - public Object getSingleResult() { - return query.getSingleResult(); - } - - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/MvcMethodInterceptor.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/MvcMethodInterceptor.java deleted file mode 100644 index fba74734..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/executor/MvcMethodInterceptor.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.codingapi.springboot.fast.executor; - -import com.codingapi.springboot.fast.annotation.FastMapping; -import lombok.AllArgsConstructor; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import java.lang.reflect.Method; - -@AllArgsConstructor -public class MvcMethodInterceptor implements MethodInterceptor { - - private final JpaExecutor jpaExecutor; - - @Override - public Object invoke(MethodInvocation invocation) - throws Throwable { - Method method = invocation.getMethod(); - Object[] args = invocation.getArguments(); - - if (method.equals(Object.class.getMethod("equals", Object.class))) { - return false; - } - - if (method.equals(Object.class.getMethod("hashCode"))) { - return hashCode(); - } - - FastMapping fastMapping = method.getAnnotation(FastMapping.class); - if (fastMapping != null) { - Class returnType = method.getReturnType(); - return jpaExecutor.execute(fastMapping.value(), fastMapping.countQuery(), args, returnType); - } - // mvc mapping proxy can't execute return null. - return null; - } - - -} \ No newline at end of file diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQuery.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQuery.java new file mode 100644 index 00000000..0a3e8be0 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQuery.java @@ -0,0 +1,97 @@ +package com.codingapi.springboot.fast.jdbc; + +import com.codingapi.springboot.fast.jpa.SQLBuilder; +import org.apache.commons.text.CaseUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class JdbcQuery { + + private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate; + + public JdbcQuery(org.springframework.jdbc.core.JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private static class CamelCaseRowMapper implements RowMapper> { + + @Override + public Map mapRow(ResultSet rs, int rowNum) throws SQLException { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + Map map = new HashMap<>(columnCount); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnLabel(i); + map.put(CaseUtils.toCamelCase(columnName, false), rs.getObject(i)); + } + return map; + } + } + + public List> queryForMapList(SQLBuilder builder) { + return queryForMapList(builder.getSQL(), builder.getParams()); + } + + public List> queryForMapList(String sql, Object... params) { + return jdbcTemplate.query(sql, params, new CamelCaseRowMapper()); + } + + public List queryForList(SQLBuilder builder) { + return (List) queryForList(builder.getSQL(), builder.getClazz(), builder.getParams()); + } + + public List queryForList(String sql, Class clazz, Object... params) { + return jdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(clazz)); + } + + public Page queryForPage(SQLBuilder builder, PageRequest pageRequest) { + return (Page)queryForPage(builder.getSQL(), builder.getCountSQL(), builder.getClazz(), pageRequest, builder.getParams()); + } + + public Page queryForPage(String sql, String countSql, Class clazz, PageRequest pageRequest, Object... params) { + List list = jdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(clazz)); + long count = this.countQuery(countSql, params); + return new PageImpl<>(list, pageRequest, count); + } + + public Page queryForPage(String sql, Class clazz, PageRequest pageRequest, Object... params) { + String countSql = "select count(1) " + sql; + return this.queryForPage(sql, countSql, clazz, pageRequest, params); + } + + public Page> queryForMapPage(SQLBuilder builder, PageRequest pageRequest) { + return queryForMapPage(builder.getSQL(), builder.getCountSQL(), pageRequest, builder.getParams()); + } + + public Page> queryForMapPage(String sql, String countSql, PageRequest pageRequest, Object... params) { + List> list = jdbcTemplate.query(sql, params, new CamelCaseRowMapper()); + long count = this.countQuery(countSql, params); + return new PageImpl<>(list, pageRequest, count); + } + + public Page> queryForMapPage(String sql, PageRequest pageRequest, Object... params) { + String countSql = "select count(1) " + sql; + return this.queryForMapPage(sql, countSql, pageRequest, params); + } + + + private long countQuery(String sql, Object... params) { + int paramsLength = params.length; + int countSqlParamsLength = sql.split("\\?").length - 1; + Object[] newParams = new Object[countSqlParamsLength]; + if (paramsLength > countSqlParamsLength) { + System.arraycopy(params, 0, newParams, 0, countSqlParamsLength); + } + return jdbcTemplate.queryForObject(sql, newParams, Long.class); + } +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryConfiguration.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryConfiguration.java new file mode 100644 index 00000000..aa05bd84 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryConfiguration.java @@ -0,0 +1,22 @@ +package com.codingapi.springboot.fast.jdbc; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + + +@Configuration +public class JdbcQueryConfiguration { + + @Bean + public JdbcQuery jdbcQuery(JdbcTemplate jdbcTemplate) { + return new JdbcQuery(jdbcTemplate); + } + + @Bean + public JdbcQueryContextRegister jdbcQueryContextRegister(JdbcQuery jdbcQuery){ + return new JdbcQueryContextRegister(jdbcQuery); + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContext.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContext.java new file mode 100644 index 00000000..7aaa455e --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContext.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.fast.jdbc; + + +import lombok.Getter; + +public class JdbcQueryContext { + + @Getter + private static final JdbcQueryContext instance = new JdbcQueryContext(); + + private JdbcQueryContext() { + + } + + @Getter + private JdbcQuery jdbcQuery; + + void setJdbcQuery(JdbcQuery jdbcQuery) { + this.jdbcQuery = jdbcQuery; + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContextRegister.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContextRegister.java new file mode 100644 index 00000000..47ab903d --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jdbc/JdbcQueryContextRegister.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.fast.jdbc; + +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.InitializingBean; + +@AllArgsConstructor +public class JdbcQueryContextRegister implements InitializingBean { + + private JdbcQuery jdbcQuery; + + @Override + public void afterPropertiesSet() throws Exception { + JdbcQueryContext.getInstance().setJdbcQuery(jdbcQuery); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQuery.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQuery.java new file mode 100644 index 00000000..aa1ddaff --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQuery.java @@ -0,0 +1,63 @@ +package com.codingapi.springboot.fast.jpa; + +import lombok.AllArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; + +@AllArgsConstructor +public class JPAQuery { + + private final EntityManager entityManager; + + public List listQuery(SQLBuilder builder) { + return listQuery(builder.getClazz(),builder.getSQL(),builder.getParams()); + } + + public List listQuery(Class clazz, String sql, Object... params) { + TypedQuery query = entityManager.createQuery(sql, clazz); + if (params != null) { + for (int i = 0; i < params.length; i++) { + query.setParameter(i + 1, params[i]); + } + } + return query.getResultList(); + } + + public Page pageQuery(SQLBuilder builder,PageRequest pageRequest) { + return pageQuery(builder.getClazz(), builder.getSQL(), builder.getCountSQL(),pageRequest,builder.getParams()); + } + + + public Page pageQuery(Class clazz, String sql, PageRequest pageRequest, Object... params) { + return pageQuery(clazz,sql,"select count(1) " + sql,pageRequest,params); + } + + public Page pageQuery(Class clazz, String sql, String countSql, PageRequest pageRequest, Object... params) { + TypedQuery query = entityManager.createQuery(sql, clazz); + if (params != null) { + for (int i = 0; i < params.length; i++) { + query.setParameter(i + 1, params[i]); + } + } + query.setFirstResult(pageRequest.getPageNumber() * pageRequest.getPageSize()); + query.setMaxResults(pageRequest.getPageSize()); + return new PageImpl<>(query.getResultList(), pageRequest, countQuery(countSql, params)); + } + + + private long countQuery(String sql, Object... params) { + TypedQuery query = entityManager.createQuery(sql, Long.class); + if (params != null) { + for (int i = 0; i < params.length; i++) { + query.setParameter(i + 1, params[i]); + } + } + return query.getSingleResult(); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryConfiguration.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryConfiguration.java new file mode 100644 index 00000000..598fe0c6 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryConfiguration.java @@ -0,0 +1,21 @@ +package com.codingapi.springboot.fast.jpa; + +import javax.persistence.EntityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class JPAQueryConfiguration { + + @Bean + public JPAQuery dynamicQuery(EntityManager entityManager){ + return new JPAQuery(entityManager); + } + + @Bean + public JPAQueryContextRegister jpaQueryContextRegister(JPAQuery JPAQuery){ + return new JPAQueryContextRegister(JPAQuery); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryContextRegister.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryContextRegister.java new file mode 100644 index 00000000..b6fc7198 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JPAQueryContextRegister.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.fast.jpa; + +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.InitializingBean; + +@AllArgsConstructor +public class JPAQueryContextRegister implements InitializingBean { + + private JPAQuery JPAQuery; + + @Override + public void afterPropertiesSet() throws Exception { + JpaQueryContext.getInstance().setJPAQuery(JPAQuery); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JpaQueryContext.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JpaQueryContext.java new file mode 100644 index 00000000..e39b2d5c --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/JpaQueryContext.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.fast.jpa; + + +import lombok.Getter; + +public class JpaQueryContext { + + @Getter + private static final JpaQueryContext instance = new JpaQueryContext(); + + private JpaQueryContext() { + + } + + @Getter + private JPAQuery JPAQuery; + + void setJPAQuery(JPAQuery JPAQuery) { + this.JPAQuery = JPAQuery; + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java new file mode 100644 index 00000000..8f6bec96 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/SQLBuilder.java @@ -0,0 +1,75 @@ +package com.codingapi.springboot.fast.jpa; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class SQLBuilder { + + private final StringBuilder sqlBuilder; + private final StringBuilder countSQLBuilder; + @Getter + private int index; + private final List params; + @Getter + private final Class clazz; + + public SQLBuilder(String sql) { + this(null, sql, "select count(1) from " + sql); + } + + public SQLBuilder(Class clazz, String sql) { + this(clazz, sql, "select count(1) from " + sql); + } + + public SQLBuilder(String sql,String countSql) { + this(null, sql, countSql); + } + + public SQLBuilder(Class clazz, String sql, String countSQL) { + this.countSQLBuilder = new StringBuilder(countSQL); + this.sqlBuilder = new StringBuilder(sql); + this.index = 1; + this.params = new ArrayList<>(); + this.clazz = clazz; + } + + public void append(String sql, Object value) { + if (value != null) { + sqlBuilder.append(" ").append(sql).append(index).append(" "); + countSQLBuilder.append(" ").append(sql).append(index).append(" "); + params.add(value); + index++; + } + } + + public void addParam(Object value){ + params.add(value); + index++; + } + + public void addParam(Object value,int index){ + params.add(value); + this.index = index; + } + + public void appendSql(String sql){ + sqlBuilder.append(" ").append(sql).append(" "); + countSQLBuilder.append(" ").append(sql).append(" "); + } + + public String getSQL() { + return sqlBuilder.toString(); + } + + public String getCountSQL() { + return countSQLBuilder.toString(); + } + + public Object[] getParams() { + return params.toArray(); + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/BaseRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/BaseRepository.java new file mode 100644 index 00000000..b4200b97 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/BaseRepository.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import org.springframework.core.ResolvableType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface BaseRepository extends JpaRepository { + + @SuppressWarnings("unchecked") + default Class getEntityClass() { + ResolvableType resolvableType = ResolvableType.forClass(getClass()).as(BaseRepository.class); + return (Class) resolvableType.getGeneric(0).resolve(); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicNativeRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicNativeRepository.java new file mode 100644 index 00000000..7641a488 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicNativeRepository.java @@ -0,0 +1,67 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.fast.jdbc.JdbcQueryContext; +import com.codingapi.springboot.fast.jpa.SQLBuilder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.List; +import java.util.Map; + +@NoRepositoryBean +public interface DynamicNativeRepository extends BaseRepository { + + default List> dynamicNativeListMapQuery(SQLBuilder builder) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForMapList(builder); + } + + default List> dynamicNativeListMapQuery(String sql, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForMapList(sql, params); + } + + default List dynamicNativeListQuery(String sql, Object... params) { + return dynamicNativeListQuery(getEntityClass(), sql, params); + } + + default List dynamicNativeListQuery(Class clazz, String sql, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForList(sql, clazz, params); + } + + default List dynamicNativeListQuery(SQLBuilder sqlBuilder) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForList(sqlBuilder); + } + + default Page dynamicNativePageQuery(String sql, String countSql, PageRequest request, Object... params) { + return dynamicNativePageQuery(getEntityClass(), sql, countSql, request, params); + } + + default Page dynamicNativePageQuery(String sql, PageRequest request, Object... params) { + return dynamicNativePageQuery(getEntityClass(), sql, request, params); + } + + default Page dynamicNativePageQuery(Class clazz, String sql, String countSql, PageRequest request, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForPage(sql, countSql, clazz, request, params); + } + + default Page dynamicNativePageQuery(Class clazz, String sql, PageRequest request, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForPage(sql, clazz, request, params); + } + + default Page dynamicNativePageQuery(SQLBuilder sqlBuilder, PageRequest request) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForPage(sqlBuilder, request); + } + + default Page> dynamicNativeMapPageMapQuery(SQLBuilder sqlBuilder,PageRequest request) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForMapPage(sqlBuilder,request); + } + + default Page> dynamicNativeMapPageMapQuery(String sql, String countSql, PageRequest request, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForMapPage(sql, countSql, request, params); + } + + default Page> dynamicNativeMapPageMapQuery(String sql, PageRequest request, Object... params) { + return JdbcQueryContext.getInstance().getJdbcQuery().queryForMapPage(sql, request, params); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicRepository.java new file mode 100644 index 00000000..a2fe6c76 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicRepository.java @@ -0,0 +1,43 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.fast.jpa.JpaQueryContext; +import com.codingapi.springboot.fast.jpa.SQLBuilder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.List; + +@NoRepositoryBean +@SuppressWarnings("unchecked") +public interface DynamicRepository extends BaseRepository { + + default List dynamicListQuery(SQLBuilder builder) { + return (List) JpaQueryContext.getInstance().getJPAQuery().listQuery(builder); + } + + default List dynamicListQuery(String sql, Object... params) { + return (List) JpaQueryContext.getInstance().getJPAQuery().listQuery(getEntityClass(), sql, params); + } + + default List dynamicListQuery(Class clazz, String sql, Object... params) { + return (List) JpaQueryContext.getInstance().getJPAQuery().listQuery(clazz, sql, params); + } + + default Page dynamicPageQuery(SQLBuilder builder, PageRequest request) { + return (Page) JpaQueryContext.getInstance().getJPAQuery().pageQuery(builder, request); + } + + default Page dynamicPageQuery(String sql, String countSql, PageRequest request, Object... params) { + return (Page) JpaQueryContext.getInstance().getJPAQuery().pageQuery(getEntityClass(), sql, countSql, request, params); + } + + default Page dynamicPageQuery(String sql, PageRequest request, Object... params) { + return (Page) JpaQueryContext.getInstance().getJPAQuery().pageQuery(getEntityClass(), sql, request, params); + } + + default Page dynamicPageQuery(Class clazz, String sql, String countSql, PageRequest request, Object... params) { + return (Page) JpaQueryContext.getInstance().getJPAQuery().pageQuery(clazz, sql, countSql, request, params); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicSQLBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicSQLBuilder.java new file mode 100644 index 00000000..50dc3b5b --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/DynamicSQLBuilder.java @@ -0,0 +1,175 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.framework.dto.request.Filter; +import com.codingapi.springboot.framework.dto.request.PageRequest; +import com.codingapi.springboot.framework.dto.request.RequestFilter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 动态条件查询组装 + */ +@Slf4j +class DynamicSQLBuilder { + + private final PageRequest request; + private final Class clazz; + + private final List params = new ArrayList<>(); + private int paramIndex = 1; + + public DynamicSQLBuilder(PageRequest request, Class clazz) { + this.request = request; + this.clazz = clazz; + } + + + public String getHQL() { + StringBuilder hql = new StringBuilder("FROM " + clazz.getSimpleName() + " WHERE "); + RequestFilter requestFilter = request.getRequestFilter(); + if (requestFilter.hasFilter()) { + List filters = requestFilter.getFilters(); + for (int i = 0; i < filters.size(); i++) { + Filter filter = filters.get(i); + this.buildSQL(filter, hql); + if (i != filters.size() - 1) { + hql.append(" AND "); + } + } + } + + Sort sort = request.getSort(); + if (sort.isSorted()) { + hql.append(" ORDER BY "); + List orders = sort.toList(); + for (int i = 0; i < orders.size(); i++) { + Sort.Order order = orders.get(i); + hql.append(order.getProperty()).append(" ").append(order.getDirection().name()); + if (i != orders.size() - 1) { + hql.append(","); + } + } + } + + log.debug("hql:{}", hql); + log.debug("params:{}", params); + return hql.toString(); + } + + + private void buildSQL(Filter filter, StringBuilder hql) { + if (filter.isOrFilters()) { + Filter[] orFilters = (Filter[]) filter.getValue(); + if (orFilters.length > 0) { + hql.append(" ( "); + for (int i = 0; i < orFilters.length; i++) { + Filter orFilter = orFilters[i]; + this.buildSQL(orFilter, hql); + if (i != orFilters.length - 1) { + hql.append(" OR "); + } + + } + hql.append(" )"); + } + } + + if (filter.isAndFilters()) { + Filter[] andFilters = (Filter[]) filter.getValue(); + if (andFilters.length > 0) { + hql.append(" ( "); + for (int i = 0; i < andFilters.length; i++) { + Filter andFilter = andFilters[i]; + this.buildSQL(andFilter, hql); + if (i != andFilters.length - 1) { + hql.append(" AND "); + } + } + hql.append(" )"); + } + } + + if (filter.isEqual()) { + hql.append(filter.getKey()).append(" = ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + + if (filter.isNull()) { + hql.append(filter.getKey()).append(" IS NULL "); + } + + if (filter.isNotNull()) { + hql.append(filter.getKey()).append(" IS NOT NULL "); + } + + if (filter.isNotEqual()) { + hql.append(filter.getKey()).append(" != ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + + if (filter.isLike()) { + hql.append(filter.getKey()).append(" LIKE ?").append(paramIndex); + params.add("%" + filter.getValue()[0] + "%"); + paramIndex++; + } + if (filter.isLeftLike()) { + hql.append(filter.getKey()).append(" LIKE ?").append(paramIndex); + params.add("%" + filter.getValue()[0]); + paramIndex++; + } + if (filter.isRightLike()) { + hql.append(filter.getKey()).append(" LIKE ?").append(paramIndex); + params.add(filter.getValue()[0] + "%"); + paramIndex++; + } + if (filter.isIn()) { + hql.append(filter.getKey()).append(" IN (").append("?").append(paramIndex).append(")"); + params.add(Arrays.asList(filter.getValue())); + paramIndex++; + } + + if (filter.isNotIn()) { + hql.append(filter.getKey()).append(" NOT IN (").append("?").append(paramIndex).append(")"); + params.add(Arrays.asList(filter.getValue())); + paramIndex++; + } + + if (filter.isGreaterThan()) { + hql.append(filter.getKey()).append(" > ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + if (filter.isLessThan()) { + hql.append(filter.getKey()).append(" < ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + if (filter.isGreaterThanEqual()) { + hql.append(filter.getKey()).append(" >= ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + if (filter.isLessThanEqual()) { + hql.append(filter.getKey()).append(" <= ?").append(paramIndex); + params.add(filter.getValue()[0]); + paramIndex++; + } + if (filter.isBetween()) { + hql.append(filter.getKey()).append(" BETWEEN ?").append(paramIndex).append(" AND ?").append(paramIndex + 1); + params.add(filter.getValue()[0]); + params.add(filter.getValue()[1]); + paramIndex += 2; + } + } + + + public Object[] getParams() { + return params.toArray(); + } +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/ExampleBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/ExampleBuilder.java new file mode 100644 index 00000000..6b72dec3 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/ExampleBuilder.java @@ -0,0 +1,49 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.framework.dto.request.Filter; +import com.codingapi.springboot.framework.dto.request.PageRequest; +import com.codingapi.springboot.framework.dto.request.RequestFilter; +import org.springframework.beans.BeanUtils; +import org.springframework.data.domain.Example; + +import java.beans.PropertyDescriptor; + +/** + * Example组装 + */ +class ExampleBuilder { + + private final PageRequest request; + private final Class clazz; + + public ExampleBuilder(PageRequest request, Class clazz) { + this.request = request; + this.clazz = clazz; + } + + public Example getExample() { + RequestFilter requestFilter = request.getRequestFilter(); + if (!requestFilter.hasFilter()) { + return null; + } + Object entity = null; + try { + entity = clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz); + for (PropertyDescriptor descriptor : descriptors) { + String name = descriptor.getName(); + Filter value = requestFilter.getFilter(name); + if (value != null) { + try { + descriptor.getWriteMethod().invoke(entity, value.getFilterValue(descriptor.getPropertyType())); + } catch (Exception e) { + } + } + } + return (Example) Example.of(entity); + } + +} \ No newline at end of file diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/FastRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/FastRepository.java new file mode 100644 index 00000000..1cc0c6c0 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/FastRepository.java @@ -0,0 +1,43 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.framework.dto.request.PageRequest; +import com.codingapi.springboot.framework.dto.request.SearchRequest; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.NoRepositoryBean; + +/** + * 更强大的Repository对象 + * + * @param + * @param + */ +@NoRepositoryBean +public interface FastRepository extends JpaRepository, JpaSpecificationExecutor, DynamicRepository, DynamicNativeRepository { + + default Page findAll(PageRequest request) { + if (request.hasFilter()) { + Class clazz = getEntityClass(); + ExampleBuilder exampleBuilder = new ExampleBuilder(request, clazz); + return findAll(exampleBuilder.getExample(), request); + } + return findAll((org.springframework.data.domain.PageRequest) request); + } + + default Page pageRequest(PageRequest request) { + if (request.hasFilter()) { + Class clazz = getEntityClass(); + DynamicSQLBuilder dynamicSQLBuilder = new DynamicSQLBuilder(request, clazz); + return dynamicPageQuery(dynamicSQLBuilder.getHQL(), request, dynamicSQLBuilder.getParams()); + } + return findAll((org.springframework.data.domain.PageRequest) request); + } + + + default Page searchRequest(SearchRequest request) { + Class clazz = getEntityClass(); + return pageRequest(request.toPageRequest(clazz)); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/SortRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/SortRepository.java new file mode 100644 index 00000000..a47a0650 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/jpa/repository/SortRepository.java @@ -0,0 +1,39 @@ +package com.codingapi.springboot.fast.jpa.repository; + +import com.codingapi.springboot.framework.domain.ISort; +import com.codingapi.springboot.framework.dto.request.SortRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.ArrayList; +import java.util.List; + +@NoRepositoryBean +public interface SortRepository extends JpaRepository { + + + default void reSort(SortRequest request) { + if (request != null && !request.getIds().isEmpty()) { + List list = new ArrayList<>(); + int minSort = Integer.MAX_VALUE; + for (Object objectId : request.getIds()) { + ID id = (ID) objectId; + T t = getReferenceById(id); + if (t.getSort() == null) { + minSort = 0; + } else { + if (t.getSort() < minSort) { + minSort = t.getSort(); + } + } + list.add(t); + } + for (T t : list) { + t.setSort(minSort++); + } + saveAll(list); + } + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerContent.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerContent.java index 3d7d5c59..3fd6ab67 100644 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerContent.java +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerContent.java @@ -1,6 +1,6 @@ package com.codingapi.springboot.fast.manager; -import jakarta.persistence.EntityManager; +import javax.persistence.EntityManager; import lombok.Getter; public class EntityManagerContent { diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerInitializer.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerInitializer.java index ea87bea4..e77dd8b8 100644 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerInitializer.java +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/manager/EntityManagerInitializer.java @@ -1,6 +1,6 @@ package com.codingapi.springboot.fast.manager; -import jakarta.persistence.EntityManager; +import javax.persistence.EntityManager; import lombok.AllArgsConstructor; import org.springframework.beans.factory.InitializingBean; diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/MvcEndpointMapping.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/FastMvcMappingRegister.java similarity index 65% rename from springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/MvcEndpointMapping.java rename to springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/FastMvcMappingRegister.java index cf2eff3b..35d95f1e 100644 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/MvcEndpointMapping.java +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/mapping/FastMvcMappingRegister.java @@ -10,10 +10,11 @@ import java.lang.reflect.Method; @AllArgsConstructor -public class MvcEndpointMapping { +public class FastMvcMappingRegister { private final RequestMappingHandlerMapping handlerMapping; + /** * add mvc mapping * @@ -37,4 +38,25 @@ public void addMapping(String url, RequestMethod requestMethod, Object handler, handlerMapping.registerMapping(mappingInfo, handler, method); } + /** + * remove mvc mapping + * + * @param url mapping url + * @param requestMethod request method + */ + public void removeMapping(String url, RequestMethod requestMethod) { + RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); + options.setPatternParser(new PathPatternParser()); + + RequestMappingInfo mappingInfo = RequestMappingInfo + .paths(url) + .methods(requestMethod) + .produces(MediaType.APPLICATION_JSON_VALUE) + .options(options) + .build(); + + handlerMapping.unregisterMapping(mappingInfo); + } + + } diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/query/FastRepository.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/query/FastRepository.java deleted file mode 100644 index 71eeb9ae..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/query/FastRepository.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.codingapi.springboot.fast.query; - -import com.codingapi.springboot.framework.dto.request.PageRequest; -import org.springframework.core.ResolvableType; -import org.springframework.data.domain.Page; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.repository.NoRepositoryBean; - -@NoRepositoryBean -public interface FastRepository extends JpaRepository, JpaSpecificationExecutor { - - default Page findAll(PageRequest request){ - if(request.hasFilter()){ - Class clazz = getDomainClass(); - return findAll(request.getExample(clazz),request); - } - return findAll((org.springframework.data.domain.PageRequest)request); - } - - - @SuppressWarnings("unchecked") - default Class getDomainClass() { - ResolvableType resolvableType = ResolvableType.forClass(getClass()).as(FastRepository.class); - return (Class) resolvableType.getGeneric(0).resolve(); - } - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/DataFastBeanDefinitionRegistrar.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/DataFastBeanDefinitionRegistrar.java deleted file mode 100644 index 0a733ab7..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/DataFastBeanDefinitionRegistrar.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.codingapi.springboot.fast.registrar; - -import com.codingapi.springboot.fast.annotation.FastController; -import com.codingapi.springboot.framework.registrar.RegisterBeanScanner; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.type.AnnotationMetadata; - -import java.util.Set; - -@Slf4j -@Configuration -public class DataFastBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { - - - @SneakyThrows - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - RegisterBeanScanner registerBeanScanner = new RegisterBeanScanner(importingClassMetadata, FastController.class); - Set> classSet = registerBeanScanner.findTypes(); - - //register bean - for (Class clazz : classSet) { - log.info("scanner @FastController class:{}", clazz); - MvcMappingRegistrar.classSet.add(clazz); - } - } - - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/MvcMappingRegistrar.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/MvcMappingRegistrar.java deleted file mode 100644 index e6a154b8..00000000 --- a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/registrar/MvcMappingRegistrar.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.codingapi.springboot.fast.registrar; - -import com.codingapi.springboot.fast.annotation.FastMapping; -import com.codingapi.springboot.fast.exception.FastMappingErrorException; -import com.codingapi.springboot.fast.executor.JpaExecutor; -import com.codingapi.springboot.fast.executor.MvcMethodInterceptor; -import com.codingapi.springboot.fast.mapping.MvcEndpointMapping; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.aop.Advisor; -import org.springframework.aop.framework.AdvisedSupport; -import org.springframework.aop.framework.AopProxy; -import org.springframework.aop.framework.AopProxyFactory; -import org.springframework.aop.framework.DefaultAopProxyFactory; -import org.springframework.data.domain.Pageable; -import org.springframework.util.StringUtils; - -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@Slf4j -public class MvcMappingRegistrar { - protected final static Set> classSet = new HashSet<>(); - private final MvcEndpointMapping mvcEndpointMapping; - - private final AopProxyFactory proxyFactory; - - private final List advisors; - - private final MvcMethodInterceptor interceptor; - - public MvcMappingRegistrar(MvcEndpointMapping mvcEndpointMapping, - JpaExecutor jpaExecutor, - List advisors) { - this.mvcEndpointMapping = mvcEndpointMapping; - this.advisors = advisors; - this.interceptor = new MvcMethodInterceptor(jpaExecutor); - this.proxyFactory = new DefaultAopProxyFactory(); - } - - @SneakyThrows - public void registerMvcMapping() { - for (Class clazz : classSet) { - Method[] methods = clazz.getDeclaredMethods(); - for (Method method : methods) { - FastMapping fastMapping = method.getAnnotation(FastMapping.class); - if (verify(fastMapping, method)) { - AdvisedSupport advisedSupport = createAdvisedSupport(clazz); - AopProxy proxy = proxyFactory.createAopProxy(advisedSupport); - mvcEndpointMapping.addMapping(fastMapping.mapping(), fastMapping.method(), - proxy.getProxy(), method); - } - } - } - } - - private AdvisedSupport createAdvisedSupport(Class clazz) { - AdvisedSupport advisedSupport = new AdvisedSupport(clazz); - advisedSupport.setTarget(interceptor); - advisedSupport.addAdvisors(advisors); - advisedSupport.addAdvice(interceptor); - return advisedSupport; - } - - private boolean verify(FastMapping fastMapping, Method method) throws FastMappingErrorException { - if (fastMapping == null) { - return false; - } - - if (!StringUtils.hasText(fastMapping.mapping())) { - throw new FastMappingErrorException(String.format("fast method %s missing mapping .", - method.getName())); - } - - if (!StringUtils.hasText(fastMapping.value())) { - throw new FastMappingErrorException(String.format("fast mapping %s missing value .", - fastMapping.mapping())); - } - - Class[] parameterTypes = method.getParameterTypes(); - for (Class parameter : parameterTypes) { - if (Pageable.class.isAssignableFrom(parameter)) { - if (!StringUtils.hasText(fastMapping.countQuery())) { - throw new FastMappingErrorException(String.format("fast mapping %s missing countQuery .", - fastMapping.mapping())); - } - } - } - return true; - } - -} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/FastScriptMappingRegister.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/FastScriptMappingRegister.java new file mode 100644 index 00000000..9dc8583f --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/FastScriptMappingRegister.java @@ -0,0 +1,46 @@ +package com.codingapi.springboot.fast.script; + +import com.codingapi.springboot.fast.mapping.FastMvcMappingRegister; +import com.codingapi.springboot.framework.dto.response.Response; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class FastScriptMappingRegister { + + private final FastMvcMappingRegister mappingRegister; + + /** + * test dynamic mapping + * + * @param scriptMapping dynamic mapping + **/ + public void addMapping(ScriptMapping scriptMapping) { + mappingRegister.addMapping(scriptMapping.getMapping(), scriptMapping.getScriptMethod().toRequestMethod(), + scriptMapping, scriptMapping.getExecuteMethod()); + } + + + /** + * test dynamic mapping + * + * @param scriptMapping dynamic mapping + * @return result + */ + public Response test(ScriptMapping scriptMapping) { + return scriptMapping.execute(); + } + + + /** + * remove mvc mapping + * + * @param url mapping url + * @param requestMethod request method + */ + public void removeMapping(String url, ScriptMethod scriptMethod){ + mappingRegister.removeMapping(url, scriptMethod.toRequestMethod()); + } + + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMapping.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMapping.java new file mode 100644 index 00000000..3fc32f3b --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMapping.java @@ -0,0 +1,56 @@ +package com.codingapi.springboot.fast.script; + +import com.codingapi.springboot.framework.dto.response.MultiResponse; +import com.codingapi.springboot.framework.dto.response.Response; +import com.codingapi.springboot.framework.dto.response.SingleResponse; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.lang.reflect.Method; +import java.util.List; + + +@Setter +@Getter +public class ScriptMapping { + + private String mapping; + private ScriptMethod scriptMethod; + private String script; + + + public ScriptMapping(String mapping, ScriptMethod scriptMethod, String script) { + this.mapping = mapping; + this.scriptMethod = scriptMethod; + this.script = script; + } + + @ResponseBody + Response execute() { + Object result = ScriptRuntime.running(script); + if (result instanceof List || result.getClass().isArray()) { + return SingleResponse.of(result); + } else { + if (result instanceof MultiResponse) { + return (MultiResponse) result; + } + if (result instanceof Page) { + return MultiResponse.of((Page) result); + } + return SingleResponse.of(result); + } + } + + + Method getExecuteMethod() { + try { + return this.getClass().getDeclaredMethod("execute"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMethod.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMethod.java new file mode 100644 index 00000000..8dfb44b6 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptMethod.java @@ -0,0 +1,18 @@ +package com.codingapi.springboot.fast.script; + +import org.springframework.web.bind.annotation.RequestMethod; + +public enum ScriptMethod { + + GET, POST; + + + public RequestMethod toRequestMethod() { + if (this == GET) { + return RequestMethod.GET; + } else { + return RequestMethod.POST; + } + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRequest.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRequest.java new file mode 100644 index 00000000..9b9f0e78 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRequest.java @@ -0,0 +1,46 @@ +package com.codingapi.springboot.fast.script; + +import javax.servlet.http.HttpServletRequest; +import com.codingapi.springboot.framework.dto.request.PageRequest; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ScriptRequest { + + private final HttpServletRequest request; + + public String getParameter(String key, String defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : result; + } + + public int getParameter(String key, int defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : Integer.parseInt(result); + } + + public float getParameter(String key, float defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : Float.parseFloat(result); + } + + public double getParameter(String key, double defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : Double.parseDouble(result); + } + + public long getParameter(String key, long defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : Long.parseLong(result); + } + + public boolean getParameter(String key, boolean defaultValue) { + String result = request.getParameter(key); + return result == null ? defaultValue : Boolean.parseBoolean(result); + } + + public PageRequest pageRequest(int pageNumber, int pageSize) { + return PageRequest.of(pageNumber, pageSize); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRuntime.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRuntime.java new file mode 100644 index 00000000..2b5a63ba --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/script/ScriptRuntime.java @@ -0,0 +1,31 @@ +package com.codingapi.springboot.fast.script; + +import com.codingapi.springboot.fast.jdbc.JdbcQuery; +import com.codingapi.springboot.fast.jdbc.JdbcQueryContext; +import com.codingapi.springboot.fast.jpa.JPAQuery; +import com.codingapi.springboot.fast.jpa.JpaQueryContext; +import groovy.lang.Binding; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + + +public class ScriptRuntime { + + static Object running(String script) { + Binding binding = new Binding(); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + ScriptRequest request = new ScriptRequest(attributes.getRequest()); + JdbcQuery jdbcQuery = JdbcQueryContext.getInstance().getJdbcQuery(); + JPAQuery jpaQuery = JpaQueryContext.getInstance().getJPAQuery(); + + binding.setVariable("$request", request); + binding.setVariable("$jpa", jpaQuery); + binding.setVariable("$jdbc", jdbcQuery); + + GroovyShell groovyShell = new GroovyShell(binding); + Script userScript = groovyShell.parse(script); + return userScript.run(); + } +} diff --git a/springboot-starter-data-fast/src/main/resources/META-INF/spring.factories b/springboot-starter-data-fast/src/main/resources/META-INF/spring.factories index 545e7ce2..f2d11256 100644 --- a/springboot-starter-data-fast/src/main/resources/META-INF/spring.factories +++ b/springboot-starter-data-fast/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.codingapi.springboot.fast.DataFastConfiguration,\ -com.codingapi.springboot.fast.registrar.DataFastBeanDefinitionRegistrar \ No newline at end of file +com.codingapi.springboot.fast.jpa.JPAQueryConfiguration,\ +com.codingapi.springboot.fast.jdbc.JdbcQueryConfiguration \ No newline at end of file diff --git a/springboot-starter-data-fast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-data-fast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c1debcb6..68b400af 100644 --- a/springboot-starter-data-fast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/springboot-starter-data-fast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ com.codingapi.springboot.fast.DataFastConfiguration -com.codingapi.springboot.fast.registrar.DataFastBeanDefinitionRegistrar \ No newline at end of file +com.codingapi.springboot.fast.jpa.JPAQueryConfiguration +com.codingapi.springboot.fast.jdbc.JdbcQueryConfiguration \ No newline at end of file diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DataFastApplicationTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DataFastApplicationTest.java deleted file mode 100644 index 1870f2c4..00000000 --- a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DataFastApplicationTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.codingapi.springboot.fast; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@AutoConfigureMockMvc -@SpringBootTest -public class DataFastApplicationTest { - - @Autowired - private MockMvc mockMvc; - - @Test - void findAll() throws Exception { - mockMvc.perform(get("/demo/findAll").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); - } - - @Test - void findById() throws Exception { - mockMvc.perform(get("/demo/getById").param("id", "1").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); - } - - @Test - void findPage() throws Exception { - mockMvc.perform(get("/demo/findPage").param("pageSize", "20").param("current", "1").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); - } -} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DemoRepositoryTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DemoRepositoryTest.java index 283fd318..9da2c24c 100644 --- a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DemoRepositoryTest.java +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/DemoRepositoryTest.java @@ -1,17 +1,24 @@ package com.codingapi.springboot.fast; import com.codingapi.springboot.fast.entity.Demo; +import com.codingapi.springboot.fast.jpa.SQLBuilder; import com.codingapi.springboot.fast.repository.DemoRepository; +import com.codingapi.springboot.framework.dto.request.Filter; import com.codingapi.springboot.framework.dto.request.PageRequest; +import com.codingapi.springboot.framework.dto.request.Relation; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +@Slf4j @SpringBootTest public class DemoRepositoryTest { @@ -20,17 +27,17 @@ public class DemoRepositoryTest { @Test - void test(){ + void test() { demoRepository.deleteAll(); Demo demo = new Demo(); demo.setName("123"); demoRepository.save(demo); - assertTrue(demo.getId()>0); + assertTrue(demo.getId() > 0); } @Test - void query(){ + void findAll() { demoRepository.deleteAll(); Demo demo1 = new Demo(); demo1.setName("123"); @@ -43,15 +50,72 @@ void query(){ PageRequest request = new PageRequest(); request.setCurrent(1); request.setPageSize(10); - request.addFilter("name","123"); + request.addFilter("name", "123"); - Page page = demoRepository.findAll(request); + Page page = demoRepository.findAll(request); assertEquals(1, page.getTotalElements()); } + @Test + void pageRequestIsNull() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demo1 = demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.addFilter("name", Relation.IS_NULL); + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } + + @Test + void pageRequestIsNotNull() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demo1 = demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.addFilter("name", Relation.IS_NOT_NULL); + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } @Test - void sort(){ + void pageRequestNotEqual() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demo1 = demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + request.addFilter("id", Relation.NOT_EQUAL, demo1.getId()); + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } + + @Test + void pageRequest() { demoRepository.deleteAll(); Demo demo1 = new Demo(); demo1.setName("123"); @@ -64,10 +128,165 @@ void sort(){ PageRequest request = new PageRequest(); request.setCurrent(1); request.setPageSize(10); + request.addFilter("name", Relation.LIKE, "%2%"); + + Page page = demoRepository.pageRequest(request); + assertEquals(1, page.getTotalElements()); + } + + + @Test + void customInSearch() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + + request.addFilter("id", Relation.IN, 1, 2, 3); + + Page page = demoRepository.pageRequest(request); + System.out.println(page.getContent()); + } + + + @Test + void customNotInSearch() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(1); + request.setPageSize(10); + + request.addFilter("id", Relation.NOT_IN, 3); + + Page page = demoRepository.pageRequest(request); + assertEquals(2, page.getTotalElements()); + } + + + @Test + void customOrSearch() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(0); + request.setPageSize(10); + + +// request.andFilter(Filter.as("id", Relation.IN, 1, 2, 3), Filter.as("name", "123")); + request.addFilter("name", "456").orFilters(Filter.as("id", Relation.IN, 1, 2, 3), Filter.as("name", "123")); request.addSort(Sort.by("id").descending()); - Page page = demoRepository.findAll(request); - assertEquals(page.getContent().get(0).getName(),"456"); + + + + Page page = demoRepository.pageRequest(request); + log.info("demo:{}", page.getContent()); +// assertEquals(2, page.getTotalElements()); + } + + @Test + void dynamicListQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + SQLBuilder builder = new SQLBuilder(Demo.class,"from Demo where 1=1"); + String search = "12"; + builder.append("and name like ?","%"+search+"%"); + + List list = demoRepository.dynamicListQuery(builder); + assertEquals(1, list.size()); + } + + + + @Test + void dynamicNativeListQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + SQLBuilder builder = new SQLBuilder(Demo.class,"select * from t_demo where 1=1"); + String search = "12"; + builder.append("and name like ?","%"+search+"%"); + + List list = demoRepository.dynamicNativeListQuery(builder); + assertEquals(1, list.size()); + } + + + @Test + void dynamicPageQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + SQLBuilder builder = new SQLBuilder(Demo.class,"select d from Demo d where 1=1","select count(1) from Demo d where 1=1"); + String search = "12"; + builder.append("and d.name like ?","%"+search+"%"); + + Page page = demoRepository.dynamicPageQuery(builder,PageRequest.of(1, 2)); + assertEquals(1, page.getTotalElements()); + } + + + @Test + void sortQuery() { + demoRepository.deleteAll(); + Demo demo1 = new Demo(); + demo1.setName("123"); + demoRepository.save(demo1); + + Demo demo2 = new Demo(); + demo2.setName("456"); + demoRepository.save(demo2); + + PageRequest request = new PageRequest(); + request.setCurrent(0); + request.setPageSize(10); + + request.addSort(Sort.by("id").descending()); + Page page = demoRepository.findAll(request); + assertEquals(page.getContent().get(0).getName(), "456"); assertEquals(2, page.getTotalElements()); } + } diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/MenuRepositoryTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/MenuRepositoryTest.java new file mode 100644 index 00000000..00cfe07c --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/MenuRepositoryTest.java @@ -0,0 +1,60 @@ +package com.codingapi.springboot.fast; + +import com.codingapi.springboot.fast.entity.Menu; +import com.codingapi.springboot.fast.repository.MenuRepository; +import com.codingapi.springboot.framework.dto.request.PageRequest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; + +import java.util.Arrays; +import java.util.List; + +@SpringBootTest +public class MenuRepositoryTest { + + @Autowired + private MenuRepository menuRepository; + + @Test + void test() { + + Menu parent = new Menu(); + parent.setName("parent"); + parent = menuRepository.save(parent); + + + Menu menu = new Menu(); + menu.setName("test"); + menu.setParent(parent); + menuRepository.save(menu); + + + PageRequest request = PageRequest.of(0, 10); + request.addFilter("parent.id", 1); + Page page = menuRepository.pageRequest(request); + System.out.println(page.getTotalElements()); + System.out.println(page.getContent()); + } + + + @Test + void test1() { + + Menu parent = new Menu(); + parent.setName("parent"); + parent = menuRepository.save(parent); + + + Menu menu = new Menu(); + menu.setName("test"); + menu.setParent(parent); + menuRepository.save(menu); + + + List menuList = menuRepository.dynamicListQuery("from Menu m where m.id in (?1)", Arrays.asList(1, 2)); + System.out.println(menuList); + } +} + diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/UserRepositoryTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/UserRepositoryTest.java new file mode 100644 index 00000000..1047c25b --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/UserRepositoryTest.java @@ -0,0 +1,92 @@ +package com.codingapi.springboot.fast; + +import com.codingapi.springboot.fast.entity.Demo; +import com.codingapi.springboot.fast.entity.Profile; +import com.codingapi.springboot.fast.entity.User; +import com.codingapi.springboot.fast.repository.DemoRepository; +import com.codingapi.springboot.fast.repository.ProfileRepository; +import com.codingapi.springboot.fast.repository.UserRepository; +import com.codingapi.springboot.framework.dto.request.PageRequest; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j +@SpringBootTest +public class UserRepositoryTest { + + @Autowired + private DemoRepository demoRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + + + @Test + void test1() { + + Demo demo = new Demo(); + demo.setName("123"); + demoRepository.save(demo); + + + Profile profile = new Profile(); + profile.setDemo(demo); + profile.setName("123"); + profileRepository.save(profile); + + + User user = new User(); + user.setName("li"); + user.setProfile(profile); + userRepository.save(user); + + assertTrue(demo.getId()>0); + assertTrue(user.getId()>0); + + PageRequest request = new PageRequest(); + request.addFilter("profile.demo.id",1); + Page page = userRepository.pageRequest(request); + System.out.println(page.getContent()); + + } + + + + @Test + void test2() { + + Demo demo = new Demo(); + demo.setName("123"); + demoRepository.save(demo); + + + Profile profile = new Profile(); + profile.setDemo(demo); + profile.setName("123"); + profileRepository.save(profile); + + + User user = new User(); + user.setName("li"); + user.setProfile(profile); + userRepository.save(user); + + assertTrue(demo.getId()>0); + assertTrue(user.getId()>0); + + PageRequest request = new PageRequest(); + request.addFilter("name","li"); + Page page = userRepository.pageRequest(request); + System.out.println(page.getContent()); + + } + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Demo.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Demo.java index 3e9e66d8..b0c922d7 100644 --- a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Demo.java +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Demo.java @@ -2,12 +2,14 @@ import lombok.Getter; import lombok.Setter; +import lombok.ToString; -import jakarta.persistence.*; +import javax.persistence.*; @Setter @Getter @Entity +@ToString @Table(name = "t_demo") public class Demo { @Id @@ -15,4 +17,6 @@ public class Demo { private Integer id; private String name; + + private Integer sort; } diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Menu.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Menu.java new file mode 100644 index 00000000..efe7424c --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Menu.java @@ -0,0 +1,22 @@ +package com.codingapi.springboot.fast.entity; + +import javax.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@Entity +@ToString +public class Menu { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String name; + + @ManyToOne + private Menu parent; + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Profile.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Profile.java new file mode 100644 index 00000000..3ef6bb4a --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/Profile.java @@ -0,0 +1,21 @@ +package com.codingapi.springboot.fast.entity; + +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; + +@Entity +@Setter +@Getter +public class Profile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + private String name; + + @OneToOne + private Demo demo; +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/User.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/User.java new file mode 100644 index 00000000..ab0b8815 --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/entity/User.java @@ -0,0 +1,22 @@ +package com.codingapi.springboot.fast.entity; + +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; + +@Entity +@Setter +@Getter +@Table(name = "t_user") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + private String name; + + @OneToOne + private Profile profile; +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/query/FastDemoQuery.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/query/FastDemoQuery.java deleted file mode 100644 index 50dd5939..00000000 --- a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/query/FastDemoQuery.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.codingapi.springboot.fast.query; - -import com.codingapi.springboot.fast.annotation.FastController; -import com.codingapi.springboot.fast.annotation.FastMapping; -import com.codingapi.springboot.fast.entity.Demo; -import com.codingapi.springboot.framework.dto.request.PageRequest; -import com.codingapi.springboot.framework.dto.response.MultiResponse; -import com.codingapi.springboot.framework.dto.response.SingleResponse; -import org.springframework.web.bind.annotation.RequestParam; - -@FastController -public interface FastDemoQuery { - - @FastMapping(value = "select d from Demo d", mapping = "/demo/findAll") - MultiResponse findAll(); - - @FastMapping(value = "select d from Demo d where d.id = ?1", mapping = "/demo/getById") - SingleResponse getById(@RequestParam("id") int id); - - @FastMapping(value = "select d from Demo d", countQuery = "select count(d) from Demo d", mapping = "/demo/findPage") - MultiResponse findPage(PageRequest pageRequest); - -} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/DemoRepository.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/DemoRepository.java index 43b36280..30a688b1 100644 --- a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/DemoRepository.java +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/DemoRepository.java @@ -1,8 +1,8 @@ package com.codingapi.springboot.fast.repository; import com.codingapi.springboot.fast.entity.Demo; -import com.codingapi.springboot.fast.query.FastRepository; +import com.codingapi.springboot.fast.jpa.repository.FastRepository; -public interface DemoRepository extends FastRepository { +public interface DemoRepository extends FastRepository { } diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/MenuRepository.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/MenuRepository.java new file mode 100644 index 00000000..ed271005 --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/MenuRepository.java @@ -0,0 +1,9 @@ +package com.codingapi.springboot.fast.repository; + +import com.codingapi.springboot.fast.entity.Menu; +import com.codingapi.springboot.fast.jpa.repository.FastRepository; + +public interface MenuRepository extends FastRepository { + + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/ProfileRepository.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/ProfileRepository.java new file mode 100644 index 00000000..51a377e8 --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/ProfileRepository.java @@ -0,0 +1,9 @@ +package com.codingapi.springboot.fast.repository; + +import com.codingapi.springboot.fast.entity.Profile; +import com.codingapi.springboot.fast.jpa.repository.FastRepository; + +public interface ProfileRepository extends FastRepository { + + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/UserRepository.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/UserRepository.java new file mode 100644 index 00000000..e92382b0 --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.codingapi.springboot.fast.repository; + +import com.codingapi.springboot.fast.entity.User; +import com.codingapi.springboot.fast.jpa.repository.FastRepository; + +public interface UserRepository extends FastRepository { + + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/script/ScriptRuntimeTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/script/ScriptRuntimeTest.java new file mode 100644 index 00000000..19e758db --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/script/ScriptRuntimeTest.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.fast.script; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +class ScriptRuntimeTest { + + @Test + void running() { + Object res = ScriptRuntime.running("return 1"); + assertEquals(1, res); + } +} \ No newline at end of file diff --git a/springboot-starter-data-fast/src/test/resources/application.properties b/springboot-starter-data-fast/src/test/resources/application.properties index c3ec648b..559cfc80 100644 --- a/springboot-starter-data-fast/src/test/resources/application.properties +++ b/springboot-starter-data-fast/src/test/resources/application.properties @@ -1,5 +1,6 @@ server.port=8088 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:file:./test.db +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true \ No newline at end of file diff --git a/springboot-starter-flow/README.md b/springboot-starter-flow/README.md new file mode 100644 index 00000000..0bea8953 --- /dev/null +++ b/springboot-starter-flow/README.md @@ -0,0 +1,39 @@ +# springboot-starter-flow 流程引擎 + +流程引擎支持的功能需要包括: + +支持的功能如下: + +流程管理 +1. build模式的流程设计 +2. schema模式的流程设计 +3. 流程的启用与禁用 +4. 流程快照的存储 + +流程设计 +1. 支持自定义节点与节点关系 +2. 支持自定义节点的操作用户,可通过groovy脚本定义 +3. 支持流程消息标题的自定义能力,可通过groovy脚本定义 +4. 支持流程异常状态的自定义能力,可通过groovy脚本定义 +5. 提供流程操作过程中的事件,可以做业务定制与延伸 + + +流程能力 +1. 流程发起 + 在设计完成以后并启用以后,可通过FlowService对象发起流程。 +2. 流程审批 + 流程的审批支持同意与拒绝,以及审批意见的填写。 +3. 流程撤销 + 流程的发起以后,在下一节点的流程待审批且未读之前可以撤销流程。 +4. 流程转办 + 流程的审批过程中,可以将流程转办给其他人员审批。 +5. 流程委托 + 可设置用户的委托人,委托人可以代理委托人审批流程。 +6. 流程催办 + 流程的审批过程中,可以催办审批人员,催办将会发送催办事件消息。 +7. 流程查询 + 可以查询流程的待办、已办、超时、延期、全部流程等数据。 +8. 流程干预 + 设置流程管理员的人员,可以对流程进行干预,可以直接对其他人的流程进行审批。 +9. 流程延期 + 流程的审批过程中,可以延期流程的审批时间。 diff --git a/springboot-starter-flow/pom.xml b/springboot-starter-flow/pom.xml new file mode 100644 index 00000000..5100e5d9 --- /dev/null +++ b/springboot-starter-flow/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + springboot-parent + com.codingapi.springboot + 2.10.5 + + + springboot-starter-flow + springboot-starter-flow project for Spring Boot + springboot-starter-flow + + + 8 + + + + + + + com.codingapi.springboot + springboot-starter + + + + com.esotericsoftware + kryo + + + + org.apache.groovy + groovy + + + + org.apache.groovy + groovy-json + + + + org.apache.groovy + groovy-xml + + + + + + + diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java new file mode 100644 index 00000000..7948386e --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.flow; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FlowConfiguration { + + @Bean + @ConditionalOnMissingBean + public FlowFrameworkRegister flowFrameworkRegister(ApplicationContext spring) { + return new FlowFrameworkRegister(spring); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java new file mode 100644 index 00000000..5fb46b8f --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.flow; + +import com.codingapi.springboot.flow.content.FlowSessionBeanProvider; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; + +@AllArgsConstructor +public class FlowFrameworkRegister implements InitializingBean { + + private final ApplicationContext application; + + @Override + public void afterPropertiesSet() throws Exception { + FlowSessionBeanProvider.getInstance().register(application); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java new file mode 100644 index 00000000..b2d0b1a8 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java @@ -0,0 +1,55 @@ +package com.codingapi.springboot.flow.bind; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** + * 数据快照 + */ +@Setter +@Getter +@AllArgsConstructor +public class BindDataSnapshot { + + /** + * 数据快照id + */ + private long id; + /** + * 快照信息 + */ + private String snapshot; + /** + * 创建时间 + */ + private long createTime; + + /** + * 数据绑定类名称 + */ + private String clazzName; + + public BindDataSnapshot(long id,IBindData bindData) { + if (bindData == null) { + throw new IllegalArgumentException("bind data is null"); + } + this.snapshot = bindData.toJsonSnapshot(); + this.clazzName = bindData.getClass().getName(); + this.createTime = System.currentTimeMillis(); + this.id = id; + } + + public BindDataSnapshot(IBindData bindData) { + this(0,bindData); + } + + public IBindData toBindData() { + try { + return JSONObject.parseObject(snapshot, (Class) Class.forName(clazzName)); + } catch (Exception e) { + throw new IllegalArgumentException("bind data error"); + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java new file mode 100644 index 00000000..2648956d --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/FlowMapBindData.java @@ -0,0 +1,48 @@ +package com.codingapi.springboot.flow.bind; + +import com.alibaba.fastjson.JSONObject; + +import java.util.HashMap; + +/** + * 流程绑定Map数据对象,用于分布式服务下的流程对象数据传递能力 + * 该对象中,将clazzName 当做了普通的key来使用, + */ +public class FlowMapBindData extends HashMap implements IBindData { + + + /** + * 获取类名称 + * + * @return 类名称 + */ + @Override + public String getClazzName() { + return (String) this.get(CLASS_NAME_KEY); + } + + /** + * 转化为类对象 + */ + @Override + public T toJavaObject(Class clazz) { + return JSONObject.parseObject(toJsonSnapshot(), clazz); + } + + public static FlowMapBindData fromJson(String json) { + return JSONObject.parseObject(json, FlowMapBindData.class); + } + + public static FlowMapBindData fromObject(Object obj) { + return JSONObject.parseObject(JSONObject.toJSONString(obj), FlowMapBindData.class); + } + + public static FlowMapBindData fromJson(JSONObject json) { + return JSONObject.parseObject(json.toJSONString(), FlowMapBindData.class); + } + + public boolean match(String matchKey) { + String className = this.getClazzName(); + return matchKey.equals(className); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java new file mode 100644 index 00000000..5c152799 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java @@ -0,0 +1,47 @@ +package com.codingapi.springboot.flow.bind; + +import com.alibaba.fastjson.JSONObject; + +/** + * 数据绑定接口 + */ +public interface IBindData { + + String CLASS_NAME_KEY = "clazzName"; + + /** + * 数据快照 + * + * @return 数据快照 + */ + default String toJsonSnapshot() { + return JSONObject.toJSONString(this); + } + + + /** + * 获取类名称 + * + * @return 类名称 + */ + default String getClazzName() { + return this.getClass().getName(); + } + + + /** + * 类对象匹配 + */ + default boolean match(String dataKey) { + String className = this.getClazzName(); + return dataKey.equals(className); + } + + + /** + * 转化为类对象 + */ + default T toJavaObject(Class clazz) { + return (T) this; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java new file mode 100644 index 00000000..e8a07925 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java @@ -0,0 +1,153 @@ +package com.codingapi.springboot.flow.build; + +import com.codingapi.springboot.flow.domain.FlowButton; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowRelation; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.error.ErrTrigger; +import com.codingapi.springboot.flow.generator.TitleGenerator; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.utils.RandomGenerator; + +import java.util.List; + +/** + * 流程工作构建器 + */ +public class FlowWorkBuilder { + + private FlowWork work = null; + + private FlowWorkBuilder(FlowWork flowWork) { + this.work = flowWork; + } + + + public static FlowWorkBuilder builder(IFlowOperator flowOperator) { + return new FlowWorkBuilder(new FlowWork(flowOperator)); + } + + public FlowWorkBuilder description(String description) { + this.work.setDescription(description); + return this; + } + + public FlowWorkBuilder postponedMax(int postponedMax) { + this.work.setPostponedMax(postponedMax); + return this; + } + + public FlowWorkBuilder skipIfSameApprover(boolean skipIfSameApprover) { + this.work.setSkipIfSameApprover(skipIfSameApprover); + return this; + } + + public FlowWorkBuilder title(String title) { + this.work.setTitle(title); + return this; + } + + public FlowWorkBuilder schema(String schema) { + this.work.schema(schema); + return this; + } + + + public Nodes nodes() { + return new Nodes(); + } + + public Relations relations() { + return new Relations(); + } + + public FlowWork build() { + work.enable(); + return work; + } + + + public class Nodes { + + public Nodes node(String id, String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, TitleGenerator titleGenerator, ErrTrigger errTrigger, boolean editable, boolean mergeable, List buttons) { + FlowNode node = new FlowNode(id, name, code, view, NodeType.parser(code), approvalType, titleGenerator, operatorMatcher, timeout, errTrigger, editable,mergeable, buttons); + work.addNode(node); + return this; + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,false, null); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, null); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, buttons); + } + + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, null); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable,mergeable, buttons); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, List buttons) { + return node(name, code, view, approvalType, operatorMatcher, true,false, buttons); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher) { + return node(name, code, view, approvalType, operatorMatcher, true,false, null); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable,boolean mergeable, List buttons) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable,mergeable, buttons); + } + + public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable,boolean mergeable) { + return node(RandomGenerator.generateUUID(), name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable,mergeable, null); + } + + + public Relations relations() { + return new Relations(); + } + + public FlowWork build() { + work.enable(); + return work; + } + + + } + + public class Relations { + + public Relations relation(String name, String source, String target) { + return relation(name, source, target, OutTrigger.defaultOutTrigger(), 1, false); + } + + public Relations relation(String name, String source, String target, OutTrigger outTrigger, int order, boolean back) { + FlowNode from = work.getNodeByCode(source); + FlowNode to = work.getNodeByCode(target); + FlowRelation relation = new FlowRelation(RandomGenerator.generateUUID(), name, from, to, outTrigger, order, back); + work.addRelation(relation); + return this; + } + + public FlowWork build() { + work.enable(); + return work; + } + + + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java new file mode 100644 index 00000000..09217db6 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java @@ -0,0 +1,98 @@ +package com.codingapi.springboot.flow.build; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.codingapi.springboot.flow.domain.FlowButton; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowRelation; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.error.ErrTrigger; +import com.codingapi.springboot.flow.generator.TitleGenerator; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 流程设计schema读取器 + */ +public class SchemaReader { + + private final JSONObject data; + + @Getter + private final List flowNodes; + @Getter + private final List flowRelations; + + public SchemaReader(String schema) { + this.data = JSONObject.parseObject(schema); + this.flowNodes = new ArrayList<>(); + this.flowRelations = new ArrayList<>(); + this.loadNodes(); + this.loadEdges(); + } + + + private void loadNodes() { + JSONArray nodes = data.getJSONArray("nodes"); + for (int i = 0; i < nodes.size(); i++) { + JSONObject node = nodes.getJSONObject(i); + JSONObject properties = node.getJSONObject("properties"); + String code = properties.getString("code"); + String operatorMatcher = properties.getString("operatorMatcher"); + String titleGenerator = properties.getString("titleGenerator"); + String name = properties.getString("name"); + boolean editable = properties.getBoolean("editable"); + boolean mergeable = properties.getBoolean("mergeable"); + String view = properties.getString("view"); + String type = properties.getString("type"); + String approvalType = properties.getString("approvalType"); + int timeout = properties.getIntValue("timeout"); + String errTrigger = properties.getString("errTrigger"); + String id = properties.getString("id"); + List buttons = null; + if(properties.containsKey("buttons")){ + buttons = properties.getJSONArray("buttons").toJavaList(FlowButton.class); + } + FlowNode flowNode = new FlowNode(id, name, code, view, NodeType.parser(type), ApprovalType.parser(approvalType), new TitleGenerator(titleGenerator), + new OperatorMatcher(operatorMatcher), timeout, StringUtils.hasLength(errTrigger) ? new ErrTrigger(errTrigger) : null, editable,mergeable, buttons); + flowNodes.add(flowNode); + } + } + + private FlowNode getFlowNodeById(String id) { + for (FlowNode flowNode : flowNodes) { + if (flowNode.getId().equals(id)) { + return flowNode; + } + } + return null; + } + + private void loadEdges() { + JSONArray edges = data.getJSONArray("edges"); + for (int i = 0; i < edges.size(); i++) { + JSONObject edge = edges.getJSONObject(i); + String id = edge.getString("id"); + String sourceNodeId = edge.getString("sourceNodeId"); + String targetNodeId = edge.getString("targetNodeId"); + + JSONObject properties = edge.getJSONObject("properties"); + String name = properties.containsKey("name") ? properties.getString("name") : null; + String outTrigger = properties.containsKey("outTrigger") ? properties.getString("outTrigger") : OutTrigger.defaultOutTrigger().getScript(); + boolean back = properties.containsKey("back") ? properties.getBoolean("back") : false; + int order = properties.containsKey("order") ? properties.getIntValue("order") : 1; + + FlowNode source = getFlowNodeById(sourceNodeId); + FlowNode target = getFlowNodeById(targetNodeId); + + FlowRelation relation = new FlowRelation(id, name, source, target, new OutTrigger(outTrigger), order, back); + flowRelations.add(relation); + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSession.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSession.java new file mode 100644 index 00000000..60f94ae5 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSession.java @@ -0,0 +1,277 @@ +package com.codingapi.springboot.flow.content; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.FlowSourceDirection; +import com.codingapi.springboot.flow.error.NodeResult; +import com.codingapi.springboot.flow.error.OperatorResult; +import com.codingapi.springboot.flow.pojo.FlowResult; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.query.FlowRecordQuery; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.result.MessageResult; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.List; + +/** + * 流程groovy脚本回话对象 + */ +@Getter +public class FlowSession { + + // 当前的流程记录(当前审批的流程) + private final FlowRecord flowRecord; + // 当前的流程设计器 + private final FlowWork flowWork; + // 当前的流程节点 + private final FlowNode flowNode; + // 流程的创建者 + private final IFlowOperator createOperator; + // 当前的操作者(当前的操作者,非代办人) + private final IFlowOperator currentOperator; + // 流程绑定数据 + private final IBindData bindData; + // 流程审批意见 + private final Opinion opinion; + // 当前节点的审批记录 + private final List historyRecords; + // bean提供者 + private final FlowSessionBeanProvider provider; + + public FlowSession(FlowRecord flowRecord, + FlowWork flowWork, + FlowNode flowNode, + IFlowOperator createOperator, + IFlowOperator currentOperator, + IBindData bindData, + Opinion opinion, + List historyRecords) { + this.flowRecord = flowRecord; + this.flowWork = flowWork; + this.flowNode = flowNode; + this.createOperator = createOperator; + this.currentOperator = currentOperator; + this.bindData = bindData; + this.opinion = opinion; + this.historyRecords = historyRecords; + this.provider = FlowSessionBeanProvider.getInstance(); + } + + + public Object getBean(String beanName) { + return provider.getBean(beanName); + } + + + public T getBean(Class clazz) { + return provider.getBean(clazz); + } + + + /** + * 获取审批意见 + */ + public String getAdvice() { + if (opinion != null) { + return opinion.getAdvice(); + } else { + return null; + } + } + + /** + * 创建节点结果 + * + * @param nodeCode 节点code + * @return 节点结果 + */ + public NodeResult createNodeErrTrigger(String nodeCode) { + return new NodeResult(nodeCode); + } + + /** + * 创建操作者结果 + * + * @param operatorIds 操作者id + * @return 操作者结果 + */ + public OperatorResult createOperatorErrTrigger(List operatorIds) { + return new OperatorResult(operatorIds); + } + + /** + * 创建操作者结果 + * + * @param operatorIds 操作者id + * @return 操作者结果 + */ + public OperatorResult createOperatorErrTrigger(long... operatorIds) { + return new OperatorResult(operatorIds); + } + + /** + * 创建流程提醒 + * + * @param title 提醒标题 + * @return 提醒对象 + */ + public MessageResult createMessageResult(String title, String resultState) { + return MessageResult.create(title, resultState); + } + + + /** + * 创建流程提醒 + * + * @param title 提醒标题 + * @return 提醒对象 + */ + public MessageResult createMessageResult(String title) { + return MessageResult.create(title); + } + + /** + * 创建流程提醒 + * + * @param title 提醒标题 + * @param closeable 是否可关闭流程 + * @return 提醒对象 + */ + public MessageResult createMessageResult(String title, String resultState, boolean closeable) { + return MessageResult.create(title, resultState, closeable); + } + + + /** + * 创建流程提醒 + * + * @param title 提醒标题 + * @param items 提醒内容 + * @param closeable 是否可关闭流程 + * @return 提醒对象 + */ + public MessageResult createMessageResult(String title, String resultState, List items, boolean closeable) { + return MessageResult.create(title, resultState, items, closeable); + } + + /** + * 提交流程 + */ + public MessageResult submitFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + FlowResult result = flowService.submitFlow(flowRecord.getId(), currentOperator, bindData, Opinion.pass(opinion.getAdvice())); + return MessageResult.create(result); + } + + /** + * 驳回流程 + */ + public MessageResult rejectFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + FlowResult result = flowService.submitFlow(flowRecord.getId(), currentOperator, bindData, Opinion.reject(opinion.getAdvice())); + return MessageResult.create(result); + } + + /** + * 上级节点的状态是驳回状态 + * @return 上级节点的状态是驳回状态 + */ + public boolean backStateIsReject() { + if (flowRecord == null) { + return false; + } + long preId = flowRecord.getPreId(); + if (preId == 0) { + return false; + } + FlowRecordQuery flowRecordQuery = getBean(FlowRecordQuery.class); + FlowRecord preRecord = flowRecordQuery.getFlowRecordById(preId); + if (preRecord != null) { + return preRecord.getFlowSourceDirection() == FlowSourceDirection.REJECT; + } + return false; + } + + /** + * 上级节点的状态是驳回状态 + * + * @see #backStateIsReject() + */ + @Deprecated + public boolean isRejectState() { + return this.backStateIsReject(); + } + + /** + * 当前节点的状态是驳回状态 + */ + public boolean currentStateIsReject() { + if (flowRecord != null) { + return flowRecord.getFlowSourceDirection() == FlowSourceDirection.REJECT; + } + return false; + } + + + /** + * 预提交流程 + */ + public MessageResult trySubmitFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + FlowSubmitResult result = flowService.trySubmitFlow(flowRecord.getId(), currentOperator, bindData, Opinion.pass(opinion.getAdvice())); + return MessageResult.create(result); + } + + /** + * 保存流程 + */ + public void saveFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + flowService.save(flowRecord.getId(), currentOperator, bindData, opinion.getAdvice()); + } + + + /** + * 催办流程 + */ + public void urgeFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + flowService.urge(flowRecord.getId(), currentOperator); + } + + /** + * 撤回流程 + */ + public void recallFlow() { + if (flowRecord == null) { + throw new IllegalArgumentException("flow record is null"); + } + FlowService flowService = loadFlowService(); + flowService.recall(flowRecord.getId(), currentOperator); + } + + + private FlowService loadFlowService() { + return (FlowService) getBean("flowService"); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java new file mode 100644 index 00000000..06731b8c --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java @@ -0,0 +1,37 @@ +package com.codingapi.springboot.flow.content; + +import lombok.Getter; +import org.springframework.context.ApplicationContext; + +/** + * 流程回话 spring bean 提供者 + */ +public class FlowSessionBeanProvider { + + @Getter + private static final FlowSessionBeanProvider instance = new FlowSessionBeanProvider(); + + private FlowSessionBeanProvider() { + } + + private ApplicationContext spring; + + public void register(ApplicationContext spring) { + this.spring = spring; + } + + public Object getBean(String beanName) { + if (spring != null) { + return spring.getBean(beanName); + } + return null; + } + + public T getBean(Class clazz) { + if (spring != null) { + return spring.getBean(clazz); + } + return null; + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowButton.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowButton.java new file mode 100644 index 00000000..7dd5d8e5 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowButton.java @@ -0,0 +1,84 @@ +package com.codingapi.springboot.flow.domain; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.em.FlowButtonType; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.result.MessageResult; +import com.codingapi.springboot.flow.script.GroovyShellContext; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 流程按钮 + */ +@Setter +@Getter +public class FlowButton { + + /** + * 编号 + */ + private String id; + /** + * 名称 + */ + private String name; + /** + * 样式 + */ + private String style; + /** + * 事件类型 + */ + private FlowButtonType type; + /** + * 自定义事件内容 (后端脚本) + */ + private String groovy; + + /** + * 自定义事件内容 (前端脚本) + */ + private String eventKey; + + /** + * 排序 + */ + private int order; + + public boolean hasGroovy() { + return groovy != null; + } + + /** + * 执行按钮事件 + * @param flowRecord 流程记录 + * @param flowNode 节点 + * @param flowWork 流程设计器 + * @param createOperator 创建者 + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param opinion 意见 + * @param historyRecords 历史记录 + */ + public MessageResult run(FlowRecord flowRecord, + FlowNode flowNode, + FlowWork flowWork, + IFlowOperator createOperator, + IFlowOperator currentOperator, + IBindData bindData, + Opinion opinion, + List historyRecords) { + if (groovy != null) { + //执行脚本 + FlowSession session = new FlowSession(flowRecord, flowWork, flowNode, createOperator, currentOperator, bindData, opinion, historyRecords); + GroovyShellContext.ShellScript script = GroovyShellContext.getInstance().parse(groovy); + return (MessageResult) script.invokeMethod("run", session); + } + return null; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java new file mode 100644 index 00000000..fcad0edd --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java @@ -0,0 +1,357 @@ +package com.codingapi.springboot.flow.domain; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.em.FlowStatus; +import com.codingapi.springboot.flow.em.FlowType; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.error.ErrTrigger; +import com.codingapi.springboot.flow.error.ErrorResult; +import com.codingapi.springboot.flow.generator.TitleGenerator; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.codingapi.springboot.flow.serializable.FlowNodeSerializable; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 流程节点 + */ +@Getter +@AllArgsConstructor +public class FlowNode { + + public static final String CODE_START = "start"; + public static final String CODE_OVER = "over"; + + /** + * 节点id + */ + private String id; + + /** + * 节点编码 + */ + private String code; + + /** + * 节点名称 + */ + private String name; + + /** + * 节点标题创建规则 + */ + private TitleGenerator titleGenerator; + + /** + * 节点类型 | 分为发起、审批、结束 + */ + private NodeType type; + + /** + * 节点视图 + */ + private String view; + + /** + * 流程审批类型 | 分为会签、非会签 + */ + private ApprovalType approvalType; + + /** + * 操作者匹配器 + */ + private OperatorMatcher operatorMatcher; + + /** + * 是否可编辑 + */ + private boolean editable; + + /** + * 是否合并记录 + *

+ * 如果为true,则表示该节点可以合并记录 + */ + private boolean mergeable; + + + /** + * 创建时间 + */ + private long createTime; + /** + * 更新时间 + */ + private long updateTime; + + /** + * 超时时间(毫秒) + */ + private long timeout; + + /** + * 异常触发器,当流程发生异常时异常通常是指找不到审批人,将会触发异常触发器,异常触发器可以是一个节点 + */ + @Setter + private ErrTrigger errTrigger; + + /** + * 流程节点按钮 + */ + private List buttons; + + /** + * 按钮顺序 + */ + public List getButtons() { + if (buttons != null) { + return buttons.stream().sorted(Comparator.comparingInt(FlowButton::getOrder)).collect(Collectors.toList()); + } + return null; + } + + public void verify() { + if (this.titleGenerator == null) { + throw new IllegalArgumentException("titleGenerator is null"); + } + if (this.operatorMatcher == null) { + throw new IllegalArgumentException("operatorMatcher is null"); + } + if (timeout < 0) { + throw new IllegalArgumentException("timeout is less than 0"); + } + if (!StringUtils.hasLength(id)) { + throw new IllegalArgumentException("id is empty"); + } + if (!StringUtils.hasLength(code)) { + throw new IllegalArgumentException("code is empty"); + } + } + + + /** + * 从序列化对象中创建节点 + * + * @return FlowNodeSerializable 序列号节点 + */ + public FlowNodeSerializable toSerializable() { + return new FlowNodeSerializable( + this.id, + this.code, + this.name, + this.titleGenerator.getScript(), + this.type, + this.view, + this.approvalType, + this.operatorMatcher.getScript(), + this.editable, + this.mergeable, + this.createTime, + this.updateTime, + this.timeout, + this.errTrigger == null ? null : this.errTrigger.getScript(), + this.buttons + ); + } + + + public FlowNode(String id, + String name, + String code, + String view, + NodeType type, + ApprovalType approvalType, + TitleGenerator titleGenerator, + OperatorMatcher operatorMatcher, + long timeout, + ErrTrigger errTrigger, + boolean editable, + boolean mergeable, + List buttons) { + this.id = id; + this.code = code; + this.name = name; + this.titleGenerator = titleGenerator; + this.type = type; + this.view = view; + this.approvalType = approvalType; + this.operatorMatcher = operatorMatcher; + this.createTime = System.currentTimeMillis(); + this.updateTime = System.currentTimeMillis(); + this.errTrigger = errTrigger; + this.timeout = timeout; + this.editable = editable; + this.mergeable = mergeable; + this.buttons = buttons; + } + + + /** + * 加载节点的操作者 + * + * @param flowSession 操作内容 + * @return 是否匹配 + */ + public List loadFlowNodeOperator(FlowSession flowSession, FlowOperatorRepository flowOperatorRepository) { + return flowOperatorRepository.findByIds(this.operatorMatcher.matcher(flowSession)); + } + + + /** + * 创建流程记录 + * + * @param workId 流程设计id + * @param workCode 流程设计编码 + * @param processId 流程id + * @param preId 上一条流程记录id + * @param title 流程标题 + * @param createOperator 流程操作者 + * @param currentOperator 当前操作者 + * @param snapshot 快照数据 + * @return 流程记录 + */ + public FlowRecord createRecord(long workId, + String workCode, + String processId, + long preId, + String title, + IFlowOperator createOperator, + IFlowOperator currentOperator, + BindDataSnapshot snapshot, + boolean isWaiting) { + + // 当前操作者存在委托人时,才需要寻找委托人 + IFlowOperator flowOperator = currentOperator; + while (flowOperator.entrustOperator() != null) { + //寻找委托人 + flowOperator = flowOperator.entrustOperator(); + } + FlowRecord record = new FlowRecord(); + record.setProcessId(processId); + record.setNodeCode(this.code); + record.setMergeable(this.mergeable); + record.setCreateTime(System.currentTimeMillis()); + record.setWorkId(workId); + record.setWorkCode(workCode); + record.setFlowStatus(FlowStatus.RUNNING); + record.setPostponedCount(0); + record.setCreateOperator(createOperator); + record.setBindClass(snapshot.getClazzName()); + record.setCurrentOperator(flowOperator); + record.setPreId(preId); + record.setTitle(title); + record.setTimeoutTime(this.loadTimeoutTime()); + record.setFlowType(isWaiting?FlowType.WAITING:FlowType.TODO); + record.setErrMessage(null); + record.setSnapshotId(snapshot.getId()); + return record; + } + + + /** + * 获取超时时间 + * + * @return 超时时间 + */ + private long loadTimeoutTime() { + if (this.timeout > 0) { + return System.currentTimeMillis() + this.timeout; + } + return 0; + } + + /** + * 是否有任意操作者匹配 + */ + public boolean isAnyOperatorMatcher() { + return operatorMatcher.isAny(); + } + + /** + * 异常匹配 + * + * @param flowSession 操作内容 + */ + public ErrorResult errMatcher(FlowSession flowSession) { + if (errTrigger != null) { + return errTrigger.trigger(flowSession); + } + return null; + } + + /** + * 是否有异常触发器 + * + * @return 是否有异常触发器 + */ + public boolean hasErrTrigger() { + return errTrigger != null; + } + + /** + * 生成标题 + * + * @param flowSession 流程内容 + * @return 标题 + */ + public String generateTitle(FlowSession flowSession) { + return titleGenerator.generate(flowSession); + } + + + /** + * 是否会签节点 + */ + public boolean isSign() { + return approvalType == ApprovalType.SIGN; + } + + /** + * 是否非会签节点 + */ + public boolean isUnSign() { + return approvalType == ApprovalType.UN_SIGN; + } + + /** + * 是否结束节点 + */ + public boolean isOverNode() { + return CODE_OVER.equals(this.code); + } + + /** + * 是否开始节点 + */ + public boolean isStartNode() { + return CODE_START.equals(this.code); + } + + /** + * 是否传阅节点 + */ + public boolean isCirculate() { + return approvalType == ApprovalType.CIRCULATE; + } + + + public FlowButton getButton(String buttonId) { + for (FlowButton button : buttons) { + if (button.getId().equals(buttonId)) { + return button; + } + } + return null; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java new file mode 100644 index 00000000..c93dead9 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java @@ -0,0 +1,168 @@ +package com.codingapi.springboot.flow.domain; + +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.serializable.FlowRelationSerializable; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 流程关系 + */ +@Getter +@AllArgsConstructor +public class FlowRelation { + + /** + * 关系id + */ + private String id; + + /** + * 名称 + */ + private String name; + + /** + * 源节点 + */ + private FlowNode source; + + /** + * 目标节点 + */ + private FlowNode target; + + /** + * 排序 + */ + private int order; + + /** + * 是否退回 + */ + private boolean back; + + /** + * 出口触发器 + */ + private OutTrigger outTrigger; + + + /** + * 创建时间 + */ + private long createTime; + + /** + * 修改时间 + */ + private long updateTime; + + + /** + * 序列化 + * + * @return 序列化对象 + */ + public FlowRelationSerializable toSerializable() { + return new FlowRelationSerializable(id, + name, + source.getId(), + target.getId(), + order, + back, + outTrigger.getScript(), + createTime, + updateTime + ); + } + + /** + * 匹配节点 + * + * @param nodeCode 节点编码 + * @return 是否匹配 + */ + public boolean sourceMatcher(String nodeCode) { + return source.getCode().equals(nodeCode); + } + + + /** + * 重新排序 + * + * @param order 排序 + */ + public void resort(int order) { + this.order = order; + } + + + public FlowRelation(String id, String name, FlowNode source, FlowNode target, OutTrigger outTrigger, int order, boolean back) { + this.id = id; + this.name = name; + this.source = source; + this.target = target; + this.outTrigger = outTrigger; + this.order = order; + this.back = back; + this.createTime = System.currentTimeMillis(); + this.updateTime = System.currentTimeMillis(); + } + + /** + * 触发条件 + * + * @param flowSession 流程内容 + * @return 下一个节点 + */ + public FlowNode trigger(FlowSession flowSession) { + if (outTrigger.trigger(flowSession)) { + return target; + } + return null; + } + + + /** + * 验证 + */ + public void verify() { + if (!StringUtils.hasLength(id)) { + throw new RuntimeException("id is null"); + } + + if (source == null || target == null) { + throw new RuntimeException("source or target is null"); + } + + if (outTrigger == null) { + throw new RuntimeException("outTrigger is null"); + } + + if(source.getCode().equals(target.getCode())){ + throw new RuntimeException("source node code is equals target node code"); + } + + if(back){ + if(source.getType() != NodeType.APPROVAL){ + throw new RuntimeException("source node type is not approval"); + } + } + } + + public void verifyNodes(List nodes) { + if (nodes.stream().noneMatch(node -> node.getId().equals(source.getId()))) { + throw new RuntimeException("source node is not exists"); + } + + if (nodes.stream().noneMatch(node -> node.getId().equals(target.getId()))) { + throw new RuntimeException("target node is not exists"); + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java new file mode 100644 index 00000000..b6ad791a --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java @@ -0,0 +1,341 @@ +package com.codingapi.springboot.flow.domain; + +import com.codingapi.springboot.flow.build.SchemaReader; +import com.codingapi.springboot.flow.serializable.FlowWorkSerializable; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.utils.RandomGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 流程设计 + */ +@Getter +@AllArgsConstructor +public class FlowWork { + + /** + * 流程的设计id + */ + @Setter + private long id; + + /** + * 流程编码 (唯一值) + */ + @Setter + private String code; + + /** + * 流程标题 + */ + @Setter + private String title; + /** + * 流程描述 + */ + @Setter + private String description; + /** + * 流程创建者 + */ + private IFlowOperator createUser; + /** + * 创建时间 + */ + private long createTime; + /** + * 更新时间 + * 也是流程的版本号 + */ + private long updateTime; + /** + * 是否启用 + */ + private boolean enable; + + /** + * 是否跳过相同审批人,默认为false + */ + @Setter + private boolean skipIfSameApprover; + + /** + * 最大延期次数 + */ + @Setter + private int postponedMax; + + /** + * 流程的节点(发起节点) + */ + private List nodes; + + /** + * 流程的关系 + */ + private List relations; + + /** + * 界面设计脚本 + */ + private String schema; + + /** + * 构造函数 + * + * @param createUser 创建者 + */ + public FlowWork(IFlowOperator createUser) { + this.createUser = createUser; + this.createTime = System.currentTimeMillis(); + this.updateTime = System.currentTimeMillis(); + this.nodes = new ArrayList<>(); + this.relations = new ArrayList<>(); + this.enable = false; + this.postponedMax = 1; + this.code = RandomGenerator.randomString(8); + } + + + /** + * 流程设计复制 + * @return FlowWork 流程设计 + */ + public FlowWork copy(){ + if(!StringUtils.hasLength(schema)){ + throw new IllegalArgumentException("schema is empty"); + } + String schema = this.getSchema(); + for(FlowNode flowNode:this.getNodes()){ + String newId = RandomGenerator.generateUUID(); + schema = schema.replaceAll(flowNode.getId(),newId); + } + + for(FlowRelation relation:this.getRelations()){ + String newId = RandomGenerator.generateUUID(); + schema = schema.replaceAll(relation.getId(),newId); + } + + FlowWork flowWork = new FlowWork(this.createUser); + flowWork.setDescription(this.getDescription()); + flowWork.setTitle(this.getTitle()); + flowWork.setCode(RandomGenerator.randomString(8)); + flowWork.setPostponedMax(this.getPostponedMax()); + flowWork.setSkipIfSameApprover(this.isSkipIfSameApprover()); + flowWork.schema(schema); + return flowWork; + } + + + public FlowWork(String code,String title, String description, int postponedMax, IFlowOperator createUser) { + this.title = title; + this.code = code; + this.description = description; + this.postponedMax = postponedMax; + this.createUser = createUser; + this.createTime = System.currentTimeMillis(); + this.updateTime = System.currentTimeMillis(); + this.nodes = new ArrayList<>(); + this.relations = new ArrayList<>(); + this.enable = false; + } + + + public void verify() { + if (this.nodes == null || this.nodes.isEmpty()) { + throw new IllegalArgumentException("nodes is empty"); + } + if (this.relations == null || this.relations.isEmpty()) { + throw new IllegalArgumentException("relations is empty"); + } + if (!StringUtils.hasLength(title)) { + throw new IllegalArgumentException("title is empty"); + } + if (!StringUtils.hasLength(code)) { + throw new IllegalArgumentException("code is empty"); + } + + this.verifyNodes(); + this.verifyRelations(); + this.checkRelation(); + } + + + private void checkRelation() { + FlowNode startNode = getNodeByCode(FlowNode.CODE_START); + if (startNode == null) { + throw new IllegalArgumentException("start node is not exist"); + } + FlowNode overNode = getNodeByCode(FlowNode.CODE_OVER); + if (overNode == null) { + throw new IllegalArgumentException("over node is not exist"); + } + + List sourceCodes = new ArrayList<>(); + List targetCodes = new ArrayList<>(); + for (FlowRelation relation : relations) { + sourceCodes.add(relation.getSource().getCode()); + targetCodes.add(relation.getTarget().getCode()); + } + + if (!sourceCodes.contains(FlowNode.CODE_START)) { + throw new IllegalArgumentException("start node relation is not exist"); + } + + if (!targetCodes.contains(FlowNode.CODE_OVER)) { + throw new IllegalArgumentException("over node relation is not exist"); + } + + } + + + private void verifyNodes() { + List nodeCodes = new ArrayList<>(); + + for (FlowNode node : nodes) { + node.verify(); + if (nodeCodes.contains(node.getCode())) { + throw new IllegalArgumentException("node code is exist"); + } + nodeCodes.add(node.getCode()); + } + } + + + private void verifyRelations() { + for (FlowRelation relation : relations) { + relation.verify(); + + relation.verifyNodes(nodes); + } + } + + + /** + * 序列化 + * + * @return FlowSerializable 流程序列化对象 + */ + public FlowWorkSerializable toSerializable() { + return new FlowWorkSerializable( + id, + code, + title, + description, + createUser.getUserId(), + createTime, + updateTime, + skipIfSameApprover, + enable, + postponedMax, + schema, + nodes.stream().map(FlowNode::toSerializable).collect(Collectors.toCollection(ArrayList::new)), + relations.stream().map(FlowRelation::toSerializable).collect(Collectors.toCollection(ArrayList::new))); + } + + + /** + * schema解析流程设计 + * + * @param schema schema + */ + public void schema(String schema) { + SchemaReader schemaReader = new SchemaReader(schema); + this.relations = schemaReader.getFlowRelations(); + this.nodes = schemaReader.getFlowNodes(); + this.schema = schema; + this.verify(); + this.updateTime = System.currentTimeMillis(); + } + + /** + * 添加节点 + * + * @param node 节点 + */ + public void addNode(FlowNode node) { + List codes = nodes.stream().map(FlowNode::getCode).collect(Collectors.toList()); + if (codes.contains(node.getCode())) { + throw new IllegalArgumentException("node code is exist"); + } + nodes.add(node); + this.updateTime = System.currentTimeMillis(); + } + + /** + * 添加关系 + * + * @param relation 关系 + */ + public void addRelation(FlowRelation relation) { + relations.add(relation); + this.updateTime = System.currentTimeMillis(); + } + + + /** + * 获取节点 + * + * @param code 节点编码 + * @return 节点 + */ + public FlowNode getNodeByCode(String code) { + for (FlowNode node : nodes) { + if (node.getCode().equals(code)) { + return node; + } + } + return null; + } + + + /** + * 获取开始节点 + * + * @return 开始节点 + */ + public FlowNode getStartNode() { + return getNodeByCode(FlowNode.CODE_START); + } + + + /** + * 是否存在退回关系 + */ + public boolean hasBackRelation() { + return relations.stream().anyMatch(FlowRelation::isBack); + } + + + /** + * 启用检测 + */ + public void enableValidate() { + if (!this.isEnable()) { + throw new IllegalArgumentException("flow work is disable"); + } + } + + + /** + * 启用 + */ + public void enable() { + this.verify(); + this.enable = true; + } + + /** + * 禁用 + */ + public void disbale() { + this.enable = false; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java new file mode 100644 index 00000000..f9f72e78 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java @@ -0,0 +1,121 @@ +package com.codingapi.springboot.flow.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +/** + * 审批意见 + */ +@Getter +@Setter +@ToString +@NoArgsConstructor +public class Opinion { + + // 默认审批(人工审批) + public static final int TYPE_DEFAULT = 0; + // 系统自动审批 + public static final int TYPE_AUTO = 1; + + + // 审批结果 暂存 + public static final int RESULT_SAVE = 0; + // 审批结果 转办 + public static final int RESULT_TRANSFER = 1; + // 审批结果 通过 + public static final int RESULT_PASS = 2; + // 审批结果 驳回 + public static final int RESULT_REJECT = 3; + // 审批结果 抄送 + public static final int RESULT_CIRCULATE = 4; + // 审批结果 等待 + public static final int RESULT_WAITING = 5; + + + /** + * 审批意见 + */ + private String advice; + /** + * 审批结果 + */ + private int result; + /** + * 意见类型 + */ + private int type; + + /** + * 指定流程的操作者 + * operatorIds 为空时,表示不指定操作者,由流程配置的操作者匹配器决定 + */ + private List operatorIds; + + public Opinion(String advice, int result, int type) { + this.advice = advice; + this.result = result; + this.type = type; + } + + public Opinion specify(List operatorIds) { + this.operatorIds = operatorIds; + return this; + } + + public Opinion specify(long... operatorIds) { + List operatorIdList = new ArrayList<>(); + for (long operatorId : operatorIds) { + operatorIdList.add(operatorId); + } + return specify(operatorIdList); + } + + public static Opinion save(String advice) { + return new Opinion(advice, RESULT_SAVE, TYPE_DEFAULT); + } + + public static Opinion pass(String advice) { + return new Opinion(advice, RESULT_PASS, TYPE_DEFAULT); + } + + public static Opinion reject(String advice) { + return new Opinion(advice, RESULT_REJECT, TYPE_DEFAULT); + } + + public static Opinion transfer(String advice) { + return new Opinion(advice, RESULT_TRANSFER, TYPE_DEFAULT); + } + + public static Opinion waiting(String advice) { + return new Opinion(advice, RESULT_WAITING, TYPE_DEFAULT); + } + + public static Opinion unSignAutoSuccess() { + return new Opinion("非会签自动审批", RESULT_PASS, TYPE_AUTO); + } + + public static Opinion circulate() { + return new Opinion("", RESULT_CIRCULATE, TYPE_AUTO); + } + + public boolean isCirculate() { + return result == RESULT_CIRCULATE; + } + + public boolean isSuccess() { + return result == RESULT_PASS; + } + + public boolean isWaiting() { + return result == RESULT_WAITING; + } + + public boolean isReject() { + return result == RESULT_REJECT; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java new file mode 100644 index 00000000..ee58a275 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java @@ -0,0 +1,30 @@ +package com.codingapi.springboot.flow.em; + +/** + * 审批类型:会签与非会签 + */ +public enum ApprovalType { + + /** + * 会签 + */ + SIGN, + /** + * 非会签 + */ + UN_SIGN, + /** + * 传阅 + */ + CIRCULATE; + + + public static ApprovalType parser(String approvalType) { + for(ApprovalType type:values()){ + if(type.name().equalsIgnoreCase(approvalType)){ + return type; + } + } + return UN_SIGN; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowButtonType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowButtonType.java new file mode 100644 index 00000000..550766f9 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowButtonType.java @@ -0,0 +1,31 @@ +package com.codingapi.springboot.flow.em; + +public enum FlowButtonType { + + // 保存 + SAVE, + // 发起 + START, + // 提交 + SUBMIT, + // 预提交 + TRY_SUBMIT, + // 指定人员提交 + SPECIFY_SUBMIT, + // 驳回 + REJECT, + // 转办 + TRANSFER, + // 撤销 + RECALL, + // 延期 + POSTPONED, + // 催办 + URGE, + // 自定义 + CUSTOM, + // 前端 + VIEW, + // 删除 + REMOVE, +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java new file mode 100644 index 00000000..4963f388 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java @@ -0,0 +1,34 @@ +package com.codingapi.springboot.flow.em; + +/** + * 流程来源的方式 + * 包括 同意、拒绝、转办 + */ +public enum FlowSourceDirection { + + /** + * 同意 + */ + PASS, + /** + * 拒绝 + */ + REJECT, + /** + * 转办 + */ + TRANSFER, + /** + * 传阅 + */ + CIRCULATE; + + public static FlowSourceDirection parser(String type){ + for(FlowSourceDirection flowSourceDirection :values()){ + if(flowSourceDirection.name().equalsIgnoreCase(type)){ + return flowSourceDirection; + } + } + return null; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java new file mode 100644 index 00000000..7c4bceec --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java @@ -0,0 +1,27 @@ +package com.codingapi.springboot.flow.em; + +/** + * 流程状态 + * 进行中、已完成 + */ +public enum FlowStatus { + + /** + * 进行中 + */ + RUNNING, + /** + * 已完成 + */ + FINISH; + + + public static FlowStatus parser(String status) { + for (FlowStatus flowStatus : FlowStatus.values()) { + if (flowStatus.name().equalsIgnoreCase(status)) { + return flowStatus; + } + } + return RUNNING; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java new file mode 100644 index 00000000..5e36e57b --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java @@ -0,0 +1,38 @@ +package com.codingapi.springboot.flow.em; + +/** + * 流程的类型 + */ +public enum FlowType { + + /** + * 待办 + */ + TODO, + /** + * 已办 + */ + DONE, + /** + * 传阅 + */ + CIRCULATE, + /** + * 转办 + */ + TRANSFER, + /** + * 等待执行 + */ + WAITING; + + + public static FlowType parser(String type){ + for(FlowType flowType :values()){ + if(flowType.name().equalsIgnoreCase(type)){ + return flowType; + } + } + return TODO; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java new file mode 100644 index 00000000..0386f857 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java @@ -0,0 +1,34 @@ +package com.codingapi.springboot.flow.em; + +/** + * 节点类型 + */ +public enum NodeType { + + /** + * 发起 + */ + START, + /** + * 审批 + */ + APPROVAL, + /** + * 传阅 + */ + CIRCULATE, + /** + * 结束 + */ + OVER; + + + public static NodeType parser(String type) { + for (NodeType nodeType : values()) { + if (nodeType.name().equalsIgnoreCase(type)) { + return nodeType; + } + } + return APPROVAL; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java new file mode 100644 index 00000000..fbdd6751 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java @@ -0,0 +1,36 @@ +package com.codingapi.springboot.flow.error; + +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.script.GroovyShellContext; +import lombok.Getter; +import org.springframework.util.StringUtils; + +/** + * 错误触发器 + */ +public class ErrTrigger { + + @Getter + private final String script; + + private final GroovyShellContext.ShellScript runtime; + + + public ErrTrigger(String script) { + if (!StringUtils.hasLength(script)) { + throw new IllegalArgumentException("script is empty"); + } + this.script = script; + this.runtime = GroovyShellContext.getInstance().parse(script); + } + + /** + * 触发 + * + * @param flowSession 流程内容 + */ + public ErrorResult trigger(FlowSession flowSession) { + return (ErrorResult) runtime.invokeMethod("run", flowSession); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java new file mode 100644 index 00000000..96d8b272 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.flow.error; + + +/** + * 异常匹配的结果对象 + */ +public abstract class ErrorResult { + + public boolean isNode(){ + return this instanceof NodeResult; + } + + public boolean isOperator(){ + return this instanceof OperatorResult; + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java new file mode 100644 index 00000000..ebcf5fd0 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.flow.error; + +import lombok.Getter; + +/** + * 节点的异常匹配对象 + */ +@Getter +public class NodeResult extends ErrorResult{ + + private final String node; + + public NodeResult(String node) { + this.node = node; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java new file mode 100644 index 00000000..9467626a --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java @@ -0,0 +1,25 @@ +package com.codingapi.springboot.flow.error; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 操作人员的异常匹配对象 + */ +@Getter +public class OperatorResult extends ErrorResult { + + private final List operatorIds; + + public OperatorResult(List operatorIds) { + this.operatorIds = operatorIds; + } + + public OperatorResult(long... operatorIds) { + this.operatorIds = new ArrayList<>(); + Arrays.stream(operatorIds).forEach(this.operatorIds::add); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java new file mode 100644 index 00000000..d6b8f406 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java @@ -0,0 +1,111 @@ +package com.codingapi.springboot.flow.event; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.ISyncEvent; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * 流程审批事件 + */ +@Slf4j +@Getter +@ToString +public class FlowApprovalEvent implements ISyncEvent { + // 创建流程 + public static final int STATE_CREATE = 1; + // 流程审批通过 + public static final int STATE_PASS = 2; + // 流程审批拒绝 + public static final int STATE_REJECT = 3; + // 流程转办 + public static final int STATE_TRANSFER = 4; + // 流程撤销 + public static final int STATE_RECALL = 5; + // 流程完成 + public static final int STATE_FINISH = 6; + // 创建待办 + public static final int STATE_TODO = 7; + // 催办 + public static final int STATE_URGE = 8; + // 抄送 + public static final int STATE_CIRCULATE = 9; + // 保存 + public static final int STATE_SAVE = 10; + + + private final int state; + private final IFlowOperator operator; + private final FlowRecord flowRecord; + private final FlowWork flowWork; + private final IBindData bindData; + + public FlowApprovalEvent(int state, FlowRecord flowRecord, IFlowOperator operator, FlowWork flowWork, IBindData bindData) { + this.state = state; + this.operator = operator; + this.flowRecord = flowRecord; + this.flowWork = flowWork; + this.bindData = bindData; + log.debug("FlowApprovalEvent:{}", this); + } + + + public boolean match(String matchKey) { + return bindData.match(matchKey); + } + + /** + * 匹配类名 + * 当前bingData下的clazzName变成了普通的key字段了,推荐使用match(String matchKey)方法 + * @param clazz 类名 + * @return 是否匹配 + */ + @Deprecated + public boolean match(Class clazz) { + return bindData.match(clazz.getName()); + } + + public T toJavaObject(Class clazz) { + return bindData.toJavaObject(clazz); + } + + public boolean isUrge() { + return state == STATE_URGE; + } + + public boolean isTodo() { + return state == STATE_TODO; + } + + public boolean isSave() { + return state == STATE_SAVE; + } + + public boolean isCreate() { + return state == STATE_CREATE; + } + + public boolean isPass() { + return state == STATE_PASS; + } + + public boolean isReject() { + return state == STATE_REJECT; + } + + public boolean isTransfer() { + return state == STATE_TRANSFER; + } + + public boolean isRecall() { + return state == STATE_RECALL; + } + + public boolean isFinish() { + return state == STATE_FINISH; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java new file mode 100644 index 00000000..2fa5f840 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java @@ -0,0 +1,47 @@ +package com.codingapi.springboot.flow.generator; + +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.script.GroovyShellContext; +import lombok.Getter; +import org.springframework.util.StringUtils; + +/** + * 标题生成器 + */ +public class TitleGenerator { + + @Getter + private final String script; + + private final GroovyShellContext.ShellScript runtime; + + public TitleGenerator(String script) { + if (!StringUtils.hasLength(script)) { + throw new IllegalArgumentException("script is empty"); + } + this.script = script; + this.runtime = GroovyShellContext.getInstance().parse(script); + } + + + /** + * 默认标题生成器 + * + * @return 标题生成器 + */ + public static TitleGenerator defaultTitleGenerator() { + return new TitleGenerator("def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}"); + } + + + /** + * 生成标题 + * + * @param flowSession 流程内容 + * @return 标题 + */ + public String generate(FlowSession flowSession) { + return (String) this.runtime.invokeMethod("run", flowSession); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java new file mode 100644 index 00000000..e383d076 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java @@ -0,0 +1,129 @@ +package com.codingapi.springboot.flow.matcher; + +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.script.GroovyShellContext; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 操作者匹配器 + */ +public class OperatorMatcher { + + @Getter + private final String script; + + private final int state; + + private final GroovyShellContext.ShellScript runtime; + + // 指定用户 + public static final int STATE_SPECIFY = 1; + // 创建者 + public static final int STATE_CREATOR = 2; + // 任意人 + public static final int STATE_ANY = 3; + + + public boolean isAny() { + return state == STATE_ANY; + } + + public boolean isCreator() { + return state == STATE_CREATOR; + } + + public boolean isSpecify() { + return state == STATE_SPECIFY; + } + + private static int parseState(String script) { + if (script.contains("content.getCurrentOperator().getUserId()")) { + return STATE_ANY; + } else if (script.contains("content.getCreateOperator().getUserId()")) { + return STATE_CREATOR; + } else { + return STATE_SPECIFY; + } + } + + + public OperatorMatcher(String script) { + this(script, parseState(script)); + } + + public OperatorMatcher(String script, int state) { + if (!StringUtils.hasLength(script)) { + throw new IllegalArgumentException("script is empty"); + } + this.script = script; + this.state = state; + this.runtime = GroovyShellContext.getInstance().parse(script); + } + + /** + * 默认操作者匹配器 + * + * @return 操作者匹配器 + */ + public static OperatorMatcher anyOperatorMatcher() { + return new OperatorMatcher("def run(content) {return [content.getCurrentOperator().getUserId()];}", STATE_ANY); + } + + /** + * 指定操作者匹配器 + * + * @param userIds 用户ids + * @return 操作者匹配器 + */ + public static OperatorMatcher specifyOperatorMatcher(long... userIds) { + String userIdsStr = Arrays.stream(userIds).mapToObj(String::valueOf).collect(Collectors.joining(",")); + return new OperatorMatcher("def run(content) {return [" + userIdsStr + "];}", STATE_SPECIFY); + } + + /** + * 指定操作者匹配器 + * + * @param userIds 用户ids + * @return 操作者匹配器 + */ + public static OperatorMatcher specifyOperatorMatcher(List userIds) { + String userIdsStr = userIds.stream().map(String::valueOf).collect(Collectors.joining(",")); + return new OperatorMatcher("def run(content) {return [" + userIdsStr + "];}", STATE_SPECIFY); + } + + /** + * 创建者操作者匹配器 + * + * @return 操作者匹配器 + */ + public static OperatorMatcher creatorOperatorMatcher() { + return new OperatorMatcher("def run(content) {return [content.getCreateOperator().getUserId()];}", STATE_CREATOR); + } + + /** + * 匹配操作者 + * + * @param flowSession 流程内容 + * @return 是否匹配 + */ + public List matcher(FlowSession flowSession) { + List values = (List) runtime.invokeMethod("run", flowSession); + if (values == null) { + return new ArrayList<>(); + } + return values.stream().map(item -> { + if (item instanceof Number) { + return ((Number) item).longValue(); + } else { + return Long.parseLong(item.toString()); + } + }).collect(Collectors.toList()); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java new file mode 100644 index 00000000..5dc44e71 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java @@ -0,0 +1,130 @@ +package com.codingapi.springboot.flow.pojo; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.record.FlowMerge; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; + + +/** + * 流程详情的阻止对象 + */ +@Getter +public class FlowDetail { + + /** + * 当前流程 + */ + private final FlowRecord flowRecord; + /** + * 流程设计器 + */ + private final FlowWork flowWork; + /** + * 流程节点 + */ + private final FlowNode flowNode; + /** + * 历史流程 + */ + private final List historyRecords; + /** + * 绑定数据 + */ + private final IBindData bindData; + /** + * 全部的操作人 + */ + private final List operators; + + /** + * 流程创建者 + */ + private final IFlowOperator flowCreator; + + /** + * 流程创建时间 + */ + private final long flowCreateTime; + + /** + * 流程的意见 + */ + private final List opinions; + + /** + * 是否可以办理 + */ + private final boolean canHandle; + + /** + * 合并记录 + */ + private final List mergeRecords; + + + public FlowDetail(FlowRecord flowRecord, + List mergeRecords, + BindDataSnapshot snapshot, + FlowWork flowWork, + List historyRecords, + List operators, + boolean canHandle) { + this.operators = operators; + this.flowRecord = flowRecord; + this.mergeRecords = mergeRecords; + this.flowWork = flowWork; + this.bindData = snapshot.toBindData(); + this.historyRecords = historyRecords; + this.opinions = historyRecords.stream().map(FlowOpinion::new).collect(Collectors.toList()); + this.flowCreator = flowRecord.getCreateOperator(); + this.flowCreateTime = flowRecord.getCreateTime(); + this.flowNode = flowWork.getNodeByCode(flowRecord.getNodeCode()); + this.canHandle = canHandle; + } + + public FlowDetail(FlowWork flowWork, + FlowNode flowNode, + List operators, + boolean canHandle) { + this.flowWork = flowWork; + this.flowNode = flowNode; + this.operators = operators; + this.flowCreateTime = 0; + this.flowRecord = null; + this.mergeRecords = null; + this.historyRecords = null; + this.bindData = null; + this.opinions = null; + this.flowCreator = null; + this.canHandle = canHandle; + } + + @Getter + public final class FlowOpinion { + private final long recordId; + private final Opinion opinion; + private final String nodeCode; + private final String nodeName; + private final IFlowOperator operator; + private final long createTime; + + public FlowOpinion(FlowRecord flowRecord) { + this.nodeCode = flowRecord.getNodeCode(); + this.nodeName = flowWork.getNodeByCode(nodeCode).getName(); + this.recordId = flowRecord.getId(); + this.opinion = flowRecord.getOpinion(); + this.operator = flowRecord.getCurrentOperator(); + this.createTime = flowRecord.getUpdateTime(); + } + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowResult.java new file mode 100644 index 00000000..1a825418 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowResult.java @@ -0,0 +1,47 @@ +package com.codingapi.springboot.flow.pojo; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +public class FlowResult { + + private final List records; + private final FlowWork flowWork; + + public FlowResult(FlowWork flowWork, List records) { + this.flowWork = flowWork; + this.records = records; + } + + public FlowResult(FlowWork flowWork,FlowRecord flowRecord) { + this.flowWork = flowWork; + this.records = new ArrayList<>(); + this.records.add(flowRecord); + } + + + /** + * 匹配操作者的记录 + * @param operator 操作者 + * @return 记录 + */ + public List matchRecordByOperator(IFlowOperator operator){ + return records.stream().filter(record -> record.isOperator(operator)).collect(Collectors.toList()); + } + + + public boolean isOver() { + return records.stream().allMatch(FlowRecord::isOverNode); + } + + public boolean isStart() { + return records.stream().allMatch(FlowRecord::isStartNode); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowStepResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowStepResult.java new file mode 100644 index 00000000..36d49c36 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowStepResult.java @@ -0,0 +1,48 @@ +package com.codingapi.springboot.flow.pojo; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class FlowStepResult { + + private final List flowNodes; + + public FlowStepResult() { + this.flowNodes = new ArrayList<>(); + } + + public void addFlowNode(FlowNode flowNode,List operators) { + this.flowNodes.add(new FlowStepNode(flowNode.getId(), flowNode.getCode(),flowNode.getName(),flowNode.getType(),operators)); + } + + + public void print(){ + for (FlowStepNode flowNode : flowNodes) { + System.out.println("flowNode = " + flowNode.getName()); + } + } + + + @Getter + public static class FlowStepNode{ + private final String id; + private final String code; + private final String name; + private final NodeType type; + private final List operators; + + public FlowStepNode(String id, String code, String name, NodeType type,List operators) { + this.id = id; + this.code = code; + this.name = name; + this.type = type; + this.operators = operators; + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowSubmitResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowSubmitResult.java new file mode 100644 index 00000000..4bacea6e --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowSubmitResult.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.flow.pojo; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.List; + +@Getter +public class FlowSubmitResult { + + private final FlowWork flowWork; + private final FlowNode flowNode; + private final List operators; + + public FlowSubmitResult(FlowWork flowWork, FlowNode flowNode, List operators) { + this.flowWork = flowWork; + this.flowNode = flowNode; + this.operators = operators; + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/query/FlowRecordQuery.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/query/FlowRecordQuery.java new file mode 100644 index 00000000..1c365cfc --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/query/FlowRecordQuery.java @@ -0,0 +1,130 @@ +package com.codingapi.springboot.flow.query; + +import com.codingapi.springboot.flow.record.FlowRecord; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +/** + * 流程记录查询服务 + */ +public interface FlowRecordQuery { + + + /** + * 根据ID获取流程记录 + * @param id 流程记录ID + * @return 流程记录 + */ + FlowRecord getFlowRecordById(long id); + + + /** + * 查询所有流程记录 + * @param pageRequest 分页参数 + * @return 流程记录 + */ + Page findAll(PageRequest pageRequest); + + + + /** + * 查看个人的未读与待办数据 + * + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findUnReadByOperatorId(long operatorId, PageRequest pageRequest); + + + /** + * 查看个人的未读与待办数据(指定流程) + * + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findUnReadByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + + /** + * 查看个人的待办数据 + * + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findTodoByOperatorId(long operatorId, PageRequest pageRequest); + + + /** + * 查看个人的待办数据(指定流程) + * + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findTodoByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + + + /** + * 查看个人的已办数据 + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findDoneByOperatorId(long operatorId, PageRequest pageRequest); + + + /** + * 查看个人的已办数据 (指定流程) + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findDoneByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + + /** + * 查看个人的发起数据 (含待办与已办) + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findInitiatedByOperatorId(long operatorId, PageRequest pageRequest); + + + /** + * 查看个人的发起数据 (含待办与已办、指定流程) + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findInitiatedByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + + /** + * 查看个人的超时的待办流程 + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findTimeoutTodoByOperatorId(long operatorId, PageRequest pageRequest); + + /** + * 查看个人的超时的待办流程 (指定流程) + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findTimeoutTodoByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + + /** + * 查看个人的延期的待办流程 + * @param operatorId 操作人 + * @return 流程记录 + */ + Page findPostponedTodoByOperatorId(long operatorId, PageRequest pageRequest); + + + /** + * 查看个人的延期的待办流程 + * @param operatorId 操作人 + * @param workCode 流程编码 + * @return 流程记录 + */ + Page findPostponedTodoByOperatorId(long operatorId,String workCode, PageRequest pageRequest); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowBackup.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowBackup.java new file mode 100644 index 00000000..1203e142 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowBackup.java @@ -0,0 +1,61 @@ +package com.codingapi.springboot.flow.record; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.codingapi.springboot.flow.serializable.FlowWorkSerializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 流程备份 + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FlowBackup { + + /** + * 备份id + */ + private long id; + + /** + * 流程的字节码 + */ + private byte[] bytes; + + /** + * 创建时间 + */ + private long createTime; + + /** + * 流程的版本号 + * 以流程的修改时间为准 + */ + private long workVersion; + + /** + * 流程的设计id + */ + private long workId; + + /** + * 恢复流程 + * @param flowOperatorRepository 操作者仓库 + * @return 流程 + */ + public FlowWork resume(FlowOperatorRepository flowOperatorRepository) { + return FlowWorkSerializable.fromSerializable(this.bytes).toFlowWork(flowOperatorRepository); + } + + public FlowBackup(FlowWork flowWork) { + this.bytes = flowWork.toSerializable().toSerializable(); + this.workVersion = flowWork.getUpdateTime(); + this.workId = flowWork.getId(); + this.createTime = System.currentTimeMillis(); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java new file mode 100644 index 00000000..5ef07031 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowMerge.java @@ -0,0 +1,14 @@ +package com.codingapi.springboot.flow.record; + +import com.codingapi.springboot.flow.bind.IBindData; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class FlowMerge { + + private final FlowRecord flowRecord; + private final IBindData bindData; + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowProcess.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowProcess.java new file mode 100644 index 00000000..b8a4f975 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowProcess.java @@ -0,0 +1,42 @@ +package com.codingapi.springboot.flow.record; + +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.utils.RandomGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程process记录 + */ +@Getter +@AllArgsConstructor +public class FlowProcess { + + /** + * 流程id + */ + private String processId; + + /** + * 创建时间 + */ + private long createTime; + + /** + * 流程的字节码 + */ + private long backupId; + + /** + * 创建者id + */ + private long createOperatorId; + + + public FlowProcess(long backupId, IFlowOperator createOperator) { + this.processId = RandomGenerator.generateUUID(); + this.createTime = System.currentTimeMillis(); + this.backupId = backupId; + this.createOperatorId = createOperator.getUserId(); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java new file mode 100644 index 00000000..ecd28cb9 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java @@ -0,0 +1,465 @@ +package com.codingapi.springboot.flow.record; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.FlowSourceDirection; +import com.codingapi.springboot.flow.em.FlowStatus; +import com.codingapi.springboot.flow.em.FlowType; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * 流程记录 + */ +@Setter +@Getter +@ToString +public class FlowRecord { + + /** + * 流程记录id + */ + private long id; + + /** + * 上一个流程记录id + */ + private long preId; + + /** + * 工作id + */ + private long workId; + + /** + * 流程编码 + */ + private String workCode; + + /** + * 流程id + */ + private String processId; + + /** + * 节点 + */ + private String nodeCode; + + + /** + * 是否可合并 + */ + private boolean mergeable; + + /** + * 流程标题 + */ + private String title; + /** + * 当前操作者 + */ + private IFlowOperator currentOperator; + /** + * 节点状态 | 待办、已办 + */ + private FlowType flowType; + + /** + * 流转产生方式 + * 流程是退回产生的还是通过产生的 + */ + private FlowSourceDirection flowSourceDirection; + + /** + * 创建时间 + */ + private long createTime; + + /** + * 更新时间 + */ + private long updateTime; + + /** + * 完成时间 + */ + private long finishTime; + + /** + * 超时到期时间 + */ + private long timeoutTime; + + /** + * 延期次数 + */ + private int postponedCount; + + /** + * 发起者id + */ + private IFlowOperator createOperator; + /** + * 审批意见 (为办理时为空) + */ + private Opinion opinion; + /** + * 流程状态 | 进行中、已完成 + */ + private FlowStatus flowStatus; + /** + * 异常信息 + */ + private String errMessage; + + /** + * 绑定数据的类 + */ + private String bindClass; + + /** + * 绑定数据的快照 + */ + private long snapshotId; + + /** + * 是否已读 + */ + private boolean read; + + /** + * 是否干预 + */ + private boolean interfere; + + /** + * 被干预的用户 + */ + private IFlowOperator interferedOperator; + + /** + * 已读时间 + */ + private long readTime; + + /** + * 延期时间 + * + * @param postponedMax 最大延期次数 + * @param time 延期时间(毫秒) + */ + public void postponedTime(int postponedMax, long time) { + if (this.postponedCount >= postponedMax) { + throw new IllegalArgumentException("postponed count is max"); + } + this.read(); + if (this.timeoutTime == 0) { + this.timeoutTime = System.currentTimeMillis(); + } + this.timeoutTime += time; + this.postponedCount++; + this.updateTime = System.currentTimeMillis(); + } + + /** + * 是否是发起节点 + */ + public boolean isInitiated() { + return preId == 0 && this.nodeCode.equals(FlowNode.CODE_START); + } + + /** + * 已读 + */ + public void read() { + this.read = true; + this.readTime = System.currentTimeMillis(); + } + + /** + * 是否未读 + */ + public boolean isUnRead() { + return !this.read; + } + + + /** + * 更新opinion + */ + public void updateOpinion(Opinion opinion) { + this.opinion = opinion; + this.updateTime = System.currentTimeMillis(); + } + + /** + * 提交状态校验 + * 是否可以提交 + */ + public void submitStateVerify() { + if (flowStatus == FlowStatus.FINISH) { + throw new IllegalArgumentException("flow is finish"); + } + if (flowType == FlowType.DONE) { + throw new IllegalArgumentException("flow is done"); + } + } + + /** + * 提交流程 + * + * @param flowOperator 操作者 + * @param snapshot 绑定数据 + * @param opinion 意见 + * @param flowSourceDirection 流转方式 + */ + public void submitRecord(IFlowOperator flowOperator, BindDataSnapshot snapshot, Opinion opinion, FlowSourceDirection flowSourceDirection) { + if (!flowOperator.isFlowManager()) { + if (flowOperator.getUserId() != this.currentOperator.getUserId()) { + throw new IllegalArgumentException("current operator is not match"); + } + } else { + this.interferedOperator = this.currentOperator; + this.currentOperator = flowOperator; + this.interfere = true; + } + this.read(); + this.flowSourceDirection = flowSourceDirection; + this.flowType = FlowType.DONE; + this.updateTime = System.currentTimeMillis(); + this.snapshotId = snapshot.getId(); + this.bindClass = snapshot.getClazzName(); + this.opinion = opinion; + } + + + /** + * 传阅流程 + */ + public void circulate() { + this.flowSourceDirection = FlowSourceDirection.CIRCULATE; + this.flowType = FlowType.CIRCULATE; + this.updateTime = System.currentTimeMillis(); + this.opinion = Opinion.circulate(); + } + + /** + * 转交流程 + */ + public void transfer(IFlowOperator flowOperator, BindDataSnapshot snapshot, Opinion opinion) { + if (flowOperator.getUserId() != this.currentOperator.getUserId()) { + throw new IllegalArgumentException("current operator is not match"); + } + this.read(); + this.flowSourceDirection = FlowSourceDirection.TRANSFER; + this.flowType = FlowType.TRANSFER; + this.updateTime = System.currentTimeMillis(); + this.snapshotId = snapshot.getId(); + this.bindClass = snapshot.getClazzName(); + this.opinion = opinion; + } + + + /** + * 转办产生的流程记录 + * + * @param title 标题 + * @param operator 操作者 + */ + public void transferToTodo(String title, IFlowOperator operator) { + this.id = 0; + this.flowType = FlowType.TODO; + this.flowStatus = FlowStatus.RUNNING; + this.postponedCount = 0; + this.updateTime = 0; + this.readTime = 0; + this.read = false; + this.title = title; + this.opinion = null; + this.flowSourceDirection = null; + this.interfere = false; + this.interferedOperator = null; + this.currentOperator = operator; + } + + /** + * 自动提交流程 (非会签时自通审批) + * + * @param flowOperator 操作者 + * @param snapshot 绑定数据 + */ + public void autoPass(IFlowOperator flowOperator, BindDataSnapshot snapshot) { + this.read(); + this.flowSourceDirection = FlowSourceDirection.PASS; + this.currentOperator = flowOperator; + this.flowType = FlowType.DONE; + this.updateTime = System.currentTimeMillis(); + this.snapshotId = snapshot.getId(); + this.bindClass = snapshot.getClazzName(); + this.opinion = Opinion.unSignAutoSuccess(); + } + + + /** + * 完成流程 + */ + public void finish() { + this.flowStatus = FlowStatus.FINISH; + this.finishTime = System.currentTimeMillis(); + } + + + /** + * 是否已审批 + */ + public boolean isDone() { + return this.flowType == FlowType.DONE; + } + + /** + * 是否完成 + */ + public boolean isFinish() { + return this.flowStatus == FlowStatus.FINISH; + } + + /** + * 是否等待 + */ + public boolean isWaiting() { + return this.flowType == FlowType.WAITING; + } + + /** + * 是否是待办 + */ + public boolean isTodo() { + return this.flowType == FlowType.TODO && this.flowStatus == FlowStatus.RUNNING; + } + + /** + * 是否是转交 + * + * @return 是否是转交 + */ + public boolean isTransfer() { + return this.flowType == FlowType.TRANSFER; + } + + + /** + * 拒绝状态 + */ + public boolean isReject() { + return this.opinion != null && this.opinion.isReject() && isDone(); + } + + /** + * 审批通过 + */ + public boolean isPass() { + return this.opinion != null && this.opinion.isSuccess() && isDone(); + } + + /** + * 匹配操作者 + * + * @param currentOperator 当前操作者 + */ + public void matcherOperator(IFlowOperator currentOperator) { + if (!isOperator(currentOperator)) { + throw new IllegalArgumentException("current operator is not match"); + } + } + + /** + * 是否是当前操作者 + * + * @param operator 操作者 + * @return 是否是当前操作者 + */ + public boolean isOperator(IFlowOperator operator) { + return this.currentOperator.getUserId() == operator.getUserId(); + } + + + /** + * 撤回流程 + */ + public void recall() { + this.flowType = FlowType.TODO; + this.updateTime = System.currentTimeMillis(); + } + + + /** + * 复制流程记录 + */ + public FlowRecord copy() { + FlowRecord record = new FlowRecord(); + record.setId(this.id); + record.setPostponedCount(this.postponedCount); + record.setPreId(this.preId); + record.setWorkId(this.workId); + record.setWorkCode(this.workCode); + record.setProcessId(this.processId); + record.setNodeCode(this.nodeCode); + record.setMergeable(this.mergeable); + record.setTitle(this.title); + record.setCurrentOperator(this.currentOperator); + record.setFlowType(this.flowType); + record.setFlowSourceDirection(this.flowSourceDirection); + record.setCreateTime(this.createTime); + record.setUpdateTime(this.updateTime); + record.setFinishTime(this.finishTime); + record.setTimeoutTime(this.timeoutTime); + record.setCreateOperator(this.createOperator); + record.setOpinion(this.opinion); + record.setFlowStatus(this.flowStatus); + record.setErrMessage(this.errMessage); + record.setBindClass(this.bindClass); + record.setSnapshotId(this.snapshotId); + record.setRead(this.read); + record.setInterfere(this.interfere); + record.setInterferedOperator(this.interferedOperator); + record.setReadTime(this.readTime); + return record; + } + + + /** + * 是否超时 + */ + public boolean isTimeout() { + if (this.timeoutTime == 0) { + return false; + } + return System.currentTimeMillis() > this.timeoutTime; + } + + /** + * 是否延期 + */ + public boolean isPostponed() { + return this.postponedCount > 0; + } + + /** + * 是否是发起节点 + */ + public boolean isStartRecord() { + return this.preId == 0; + } + + public boolean isOverNode() { + return this.nodeCode.equals(FlowNode.CODE_OVER); + } + + public boolean isStartNode() { + return this.nodeCode.equals(FlowNode.CODE_START); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBackupRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBackupRepository.java new file mode 100644 index 00000000..5263f035 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBackupRepository.java @@ -0,0 +1,33 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowBackup; + +/** + * 流程备份仓库(流程快照仓库) + */ +public interface FlowBackupRepository { + + /** + * 备份流程 + * @param flowWork 流程 + * @return 备份对象 + */ + FlowBackup backup(FlowWork flowWork); + + /** + * 根据流程id和版本号获取备份 + * @param workId 流程id + * @param workVersion 版本号 + * @return 备份对象 + */ + FlowBackup getFlowBackupByWorkIdAndVersion(long workId,long workVersion); + + /** + * 根据备份id获取备份 + * @param backupId 备份id + * @return 备份对象 + */ + FlowBackup getFlowBackupById(long backupId); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBindDataRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBindDataRepository.java new file mode 100644 index 00000000..a56cb2c3 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBindDataRepository.java @@ -0,0 +1,31 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; + +/** + * 流程绑定数据仓库 + * 流程绑定数据即,流程的表单数据 + */ +public interface FlowBindDataRepository { + + /** + * 保存数据 + * @param snapshot 数据 + */ + void save(BindDataSnapshot snapshot); + + /** + * 更新数据 + * @param snapshot 数据 + */ + void update(BindDataSnapshot snapshot); + + + /** + * 查询快照数据 + * @param id 快照id + * @return BindDataSnapshot + */ + BindDataSnapshot getBindDataSnapshotById(long id); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowOperatorRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowOperatorRepository.java new file mode 100644 index 00000000..4f73f7d9 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowOperatorRepository.java @@ -0,0 +1,29 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.user.IFlowOperator; + +import java.util.List; + +/** + * 流程操作者 仓库 + */ +public interface FlowOperatorRepository { + + /** + * 根据ID查询流程用户 + * + * @param ids IDs + * @return List of IFlowOperator + */ + List findByIds(List ids); + + + /** + * 根据ID查询流程用户 + * + * @param id ID + * @return IFlowOperator + */ + IFlowOperator getFlowOperatorById(long id); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowProcessRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowProcessRepository.java new file mode 100644 index 00000000..acf8cfe1 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowProcessRepository.java @@ -0,0 +1,19 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowProcess; + +/** + * 流程仓库,每一个流程都会在创建时被创建一条process数据,用于标识流程 + */ +public interface FlowProcessRepository { + + void save(FlowProcess flowProcess); + + + FlowWork getFlowWorkByProcessId(String processId); + + + void deleteByProcessId(String processId); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java new file mode 100644 index 00000000..8987ec6c --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java @@ -0,0 +1,89 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.record.FlowRecord; + +import java.util.List; + + +/** + * 流转记录数据仓库 + */ +public interface FlowRecordRepository { + + /** + * 保存流程记录 + * + * @param records 流程记录 + */ + void save(List records); + + /** + * 更新流程记录 + * + * @param flowRecord 流程记录 + */ + void update(FlowRecord flowRecord); + + /** + * 根据ID查询流程记录 + * + * @param id 流程记录ID + * @return FlowRecord + */ + FlowRecord getFlowRecordById(long id); + + /** + * 根据前置ID查询流程记录 + * + * @param preId 前置ID + * @return List of FlowRecord + */ + List findFlowRecordByPreId(long preId); + + + /** + * 根据流程id查询流程记录 + * + * @param processId 流程id + */ + List findFlowRecordByProcessId(String processId); + + /** + * 获取合并的流程记录 + * @param workCode 流程编码 + * @param nodeCode 节点编码 + * @param currentOperatorId 当前操作者ID + * @return List of FlowRecord + */ + List findMergeFlowRecordById(String workCode,String nodeCode,long currentOperatorId); + + + /** + * 查询所有未完成的流程记录 + * + * @param processId 流程id + * @return List of FlowRecord + */ + List findTodoFlowRecordByProcessId(String processId); + + /** + * 根据流程id 修改所有的记录状态为已完成 + * + * @param processId 流程id + */ + void finishFlowRecordByProcessId(String processId); + + /** + * 删除流程记录 + * + * @param childrenRecords 流程记录 + */ + void delete(List childrenRecords); + + /** + * 根据流程id删除流程记录 + * @param processId 流程id + */ + void deleteByProcessId(String processId); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowWorkRepository.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowWorkRepository.java new file mode 100644 index 00000000..093e918e --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowWorkRepository.java @@ -0,0 +1,18 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; + +/** + * 流程设计器仓库 + */ +public interface FlowWorkRepository { + + FlowWork getFlowWorkById(long id); + + FlowWork getFlowWorkByCode(String code); + + void save(FlowWork flowWork); + + void delete(long id); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/MessageResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/MessageResult.java new file mode 100644 index 00000000..5bedea04 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/MessageResult.java @@ -0,0 +1,138 @@ +package com.codingapi.springboot.flow.result; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.pojo.FlowResult; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Setter +@Getter +public class MessageResult { + + /** + * 展示的标题 + */ + private String title; + + /** + * 展示状态 + */ + private ResultState resultState; + + /** + * 展示的内容 + */ + private List items; + + /** + * 是否可关闭流程 + */ + private boolean closeable; + + /** + * 添加一个展示项 + * + * @param label 标签 + * @param value 值 + * @return this + */ + public MessageResult addItem(String label, String value) { + if (items == null) { + items = new java.util.ArrayList<>(); + } + items.add(new Message(label, value)); + return this; + } + + /** + * 是否可关闭流程 + * + * @param closeable 是否可关闭 + * @return this + */ + public MessageResult closeable(boolean closeable) { + this.closeable = closeable; + return this; + } + + /** + * 展示状态 + * + * @param resultState 展示状态 + * @return this + */ + public MessageResult resultState(String resultState) { + this.resultState = ResultState.parser(resultState); + return this; + } + + + @Setter + @Getter + @AllArgsConstructor + public static class Message { + private String label; + private String value; + } + + + + public static MessageResult create(FlowSubmitResult result) { + List operators = result.getOperators(); + FlowNode flowNode = result.getFlowNode(); + MessageResult messageResult = new MessageResult(); + messageResult.setResultState(ResultState.SUCCESS); + messageResult.setTitle("下级节点提示"); + messageResult.addItem("下级审批节点", flowNode.getName()); + StringBuilder usernames = new StringBuilder(); + for (IFlowOperator operator : operators) { + usernames.append(operator.getName()).append(","); + } + messageResult.addItem("下级审批人", usernames.toString()); + return messageResult; + } + + public static MessageResult create(FlowResult result) { + List records = result.getRecords(); + FlowWork flowWork = result.getFlowWork(); + MessageResult messageResult = new MessageResult(); + messageResult.setResultState(ResultState.SUCCESS); + messageResult.setTitle("流程审批完成"); + for (FlowRecord record : records) { + FlowNode flowNode = flowWork.getNodeByCode(record.getNodeCode()); + messageResult.addItem("下级审批节点", flowNode.getName()); + messageResult.addItem("下级审批人", record.getCurrentOperator().getName()); + } + return messageResult; + } + + + public static MessageResult create(String title) { + return create(title, "SUCCESS", null, false); + } + + public static MessageResult create(String title, String resultState) { + return create(title, resultState, null, false); + } + + + public static MessageResult create(String title, String resultState, boolean closeable) { + return create(title, resultState, null, closeable); + } + + public static MessageResult create(String title, String resultState, List items, boolean closeable) { + MessageResult messageResult = new MessageResult(); + messageResult.setTitle(title); + messageResult.setItems(items); + messageResult.setResultState(ResultState.parser(resultState)); + messageResult.setCloseable(closeable); + return messageResult; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/ResultState.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/ResultState.java new file mode 100644 index 00000000..9048fd45 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/result/ResultState.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.flow.result; + +/** + * 状态数据 + */ +public enum ResultState { + SUCCESS, + INFO, + WARNING; + + + public static ResultState parser(String state) { + if ("SUCCESS".equalsIgnoreCase(state)) { + return SUCCESS; + } else if ("INFO".equalsIgnoreCase(state)) { + return INFO; + } else if ("WARNING".equalsIgnoreCase(state)) { + return WARNING; + } else { + return SUCCESS; + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/script/GroovyShellContext.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/script/GroovyShellContext.java new file mode 100644 index 00000000..356fbb6e --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/script/GroovyShellContext.java @@ -0,0 +1,87 @@ +package com.codingapi.springboot.flow.script; + +import com.codingapi.springboot.flow.utils.Sha256Utils; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class GroovyShellContext { + + @Getter + private final static GroovyShellContext instance = new GroovyShellContext(); + + private final static GroovyShell groovyShell = new GroovyShell(); + + private final Map cache = new HashMap<>(); + + private final static ExecutorService threadPool = Executors.newFixedThreadPool(10); + + // 缓存最大值 + private final static int MAX_CACHE_SIZE = 10000; + + + private GroovyShellContext() { + } + + public ShellScript parse(String script) { + String hash = Sha256Utils.generateSHA256(script); + if (cache.containsKey(hash)) { + return cache.get(hash); + } else { + if (cache.size() > MAX_CACHE_SIZE) { + cache.clear(); + } + ShellScript shellScript = new ShellScript(script); + threadPool.submit(shellScript); + cache.put(hash, shellScript); + return shellScript; + } + } + + + public int size() { + return cache.size(); + } + + public static class ShellScript implements Runnable { + + @Getter + private final String script; + + private Script runtime; + + public ShellScript(String script) { + this.script = script; + } + + public Object invokeMethod(String run, Object params) { + synchronized (ShellScript.this) { + if (runtime == null) { + try { + ShellScript.this.wait(1000); + } catch (InterruptedException ignored) { + } + if (runtime == null) { + runtime = groovyShell.parse(script); + } + } + return runtime.invokeMethod(run, params); + } + } + + @Override + public void run() { + runtime = groovyShell.parse(script); + synchronized (ShellScript.this) { + this.notifyAll(); + } + } + } + + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java new file mode 100644 index 00000000..b517104c --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java @@ -0,0 +1,105 @@ +package com.codingapi.springboot.flow.serializable; + +import com.codingapi.springboot.flow.domain.FlowButton; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.error.ErrTrigger; +import com.codingapi.springboot.flow.generator.TitleGenerator; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 流程节点序列化 + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FlowNodeSerializable implements Serializable { + + /** + * 节点id + */ + private String id; + + /** + * 节点编码 + */ + private String code; + + /** + * 节点名称 + */ + private String name; + + /** + * 节点标题创建规则 + */ + private String titleGenerator; + + /** + * 节点类型 | 分为发起、审批、结束 + */ + private NodeType type; + + /** + * 节点视图 + */ + private String view; + + /** + * 流程审批类型 | 分为会签、非会签 + */ + private ApprovalType approvalType; + + /** + * 操作者匹配器 + */ + private String operatorMatcher; + + /** + * 是否可编辑 + */ + private boolean editable; + + /** + * 是否可合并审批 + */ + private boolean mergeable; + + /** + * 创建时间 + */ + private long createTime; + /** + * 更新时间 + */ + private long updateTime; + + /** + * 超时时间(毫秒) + */ + private long timeout; + + /** + * 异常触发器,当流程发生异常时异常通常是指找不到审批人,将会触发异常触发器,异常触发器可以是一个节点 + */ + private String errTrigger; + + /** + * 流程节点按钮 + */ + private List buttons; + + public FlowNode toFlowNode() { + return new FlowNode(id, code, name, new TitleGenerator(titleGenerator), type, view, approvalType, + new OperatorMatcher(operatorMatcher), editable, mergeable, createTime, updateTime, timeout, errTrigger == null ? null : new ErrTrigger(errTrigger), buttons); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowRelationSerializable.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowRelationSerializable.java new file mode 100644 index 00000000..d2e0e0aa --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowRelationSerializable.java @@ -0,0 +1,82 @@ +package com.codingapi.springboot.flow.serializable; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowRelation; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 流程关系序列化 + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FlowRelationSerializable implements Serializable { + + /** + * 关系id + */ + private String id; + + /** + * 名称 + */ + private String name; + + /** + * 源节点 + */ + private String sourceId; + + /** + * 目标节点 + */ + private String targetId; + + /** + * 排序 + */ + private int order; + + /** + * 是否退回 + */ + private boolean back; + + /** + * 出口触发器 + */ + private String outTrigger; + + + /** + * 创建时间 + */ + private long createTime; + + /** + * 修改时间 + */ + private long updateTime; + + public FlowRelation toFlowRelation(List nodes) { + FlowNode source = null; + FlowNode target = null; + for (FlowNode node : nodes) { + if (node.getId().equals(sourceId)) { + source = node; + } + if (node.getId().equals(targetId)) { + target = node; + } + } + return new FlowRelation(id, name, source, target, order, back, new OutTrigger(outTrigger), createTime, updateTime); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowWorkSerializable.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowWorkSerializable.java new file mode 100644 index 00000000..d25d039a --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowWorkSerializable.java @@ -0,0 +1,159 @@ +package com.codingapi.springboot.flow.serializable; + +import com.codingapi.springboot.flow.domain.FlowButton; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.em.FlowButtonType; +import com.codingapi.springboot.flow.em.NodeType; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 流程序列化 + * 流程序列化为了减少数据存储的压力,该数据会存储到FlowProcess的创建过程中,为了降低数据的存储容量,将会自动判断 + * 是否存在相同的版本,若存在时则不会存储新的数据。 + * + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class FlowWorkSerializable implements Serializable { + + /** + * 流程的设计id + */ + private long id; + + /** + * 流程编号 + */ + private String code; + + /** + * 流程标题 + */ + private String title; + /** + * 流程描述 + */ + private String description; + /** + * 流程创建者 + */ + private long createUser; + /** + * 创建时间 + */ + private long createTime; + /** + * 更新时间 + */ + private long updateTime; + + /** + * 是否跳过相同审批人,默认为false + */ + @Setter + private boolean skipIfSameApprover; + + /** + * 是否启用 + */ + @Setter + private boolean enable; + + /** + * 最大延期次数 + */ + @Setter + private int postponedMax; + + /** + * 流程的schema + */ + private String schema; + + /** + * 流程的节点(发起节点) + */ + private List nodes; + + /** + * 流程的关系 + */ + private List relations; + + + /** + * 序列化 + * + * @return 序列化对象 + */ + public byte[] toSerializable() { + Kryo kryo = new Kryo(); + kryo.register(ArrayList.class); + kryo.register(FlowNodeSerializable.class); + kryo.register(FlowRelationSerializable.class); + kryo.register(FlowWorkSerializable.class); + kryo.register(ApprovalType.class); + kryo.register(NodeType.class); + kryo.register(FlowButton.class); + kryo.register(FlowButtonType.class); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + Output output = new Output(outputStream); + kryo.writeObject(output, this); + output.close(); + return outputStream.toByteArray(); + } + + + public static FlowWorkSerializable fromSerializable(byte[] bytes) { + Kryo kryo = new Kryo(); + kryo.register(ArrayList.class); + kryo.register(FlowNodeSerializable.class); + kryo.register(FlowRelationSerializable.class); + kryo.register(FlowWorkSerializable.class); + kryo.register(ApprovalType.class); + kryo.register(NodeType.class); + kryo.register(FlowButton.class); + kryo.register(FlowButtonType.class); + return kryo.readObject(new Input(bytes), FlowWorkSerializable.class); + } + + + public FlowWork toFlowWork(FlowOperatorRepository flowOperatorRepository) { + List flowNodes = nodes.stream().map(FlowNodeSerializable::toFlowNode).collect(Collectors.toList()); + return new FlowWork( + id, + code, + title, + description, + flowOperatorRepository.getFlowOperatorById(createUser), + createTime, + updateTime, + enable, + skipIfSameApprover, + postponedMax, + flowNodes, + relations.stream().map((item) -> item.toFlowRelation(flowNodes)).collect(Collectors.toList()), + schema + ); + } + + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowDirectionService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowDirectionService.java new file mode 100644 index 00000000..367efa68 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowDirectionService.java @@ -0,0 +1,121 @@ +package com.codingapi.springboot.flow.service; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.FlowSourceDirection; +import com.codingapi.springboot.flow.record.FlowRecord; +import lombok.Getter; + +import java.util.List; + +/** + * 流程方向服务 + */ +public class FlowDirectionService { + + private final FlowWork flowWork; + private final FlowNode flowNode; + private final Opinion opinion; + + @Getter + private FlowSourceDirection flowSourceDirection; + + + private List historyRecords; + + public FlowDirectionService(FlowNode flowNode, FlowWork flowWork, Opinion opinion) { + this.flowNode = flowNode; + this.opinion = opinion; + this.flowWork = flowWork; + } + + + public void bindHistoryRecords(List historyRecords) { + this.historyRecords = historyRecords; + } + + + /** + * 解析当前的审批方向 + */ + public void loadFlowSourceDirection() { + if (opinion.isSuccess() || opinion.isWaiting()) { + flowSourceDirection = FlowSourceDirection.PASS; + } + if (opinion.isReject()) { + flowSourceDirection = FlowSourceDirection.REJECT; + } + } + + /** + * 重新加载审批方向 + * 根据会签结果判断是否需要重新设置审批方向 + */ + public FlowSourceDirection reloadFlowSourceDirection() { + if (flowNode.isSign()) { + boolean allPass = historyRecords.stream().filter(item -> !item.isTransfer()) + .allMatch(item-> item.isPass() || item.getOpinion().isWaiting()); + if (!allPass) { + flowSourceDirection = FlowSourceDirection.REJECT; + } + } + return flowSourceDirection; + } + + + /** + * 校验流程的审批方向 + */ + public void verifyFlowSourceDirection() { + if (flowSourceDirection == null) { + throw new IllegalArgumentException("flow source direction is null"); + } + if (flowNode.isStartNode() && flowSourceDirection == FlowSourceDirection.REJECT) { + throw new IllegalArgumentException("flow node is start node"); + } + } + + /** + * 判断当前流程节点是否已经完成,是否可以继续流转 + */ + public boolean hasCurrentFlowNodeIsDone() { + // 会签下所有人尚未提交时,不执行下一节点 + return historyRecords.stream().filter(item -> !item.isTransfer()).allMatch(FlowRecord::isDone); + } + + + /** + * 检测当前流程是否已经完成 + * 即流程已经进行到了最终节点且审批意见为同意 + */ + public boolean hasCurrentFlowIsFinish() { + if (flowSourceDirection == FlowSourceDirection.PASS && flowNode.isOverNode()) { + return true; + } + return false; + } + + + /** + * 判断当前流程是否为默认的驳回流程 + */ + public boolean isDefaultBackRecord() { + return flowSourceDirection == FlowSourceDirection.REJECT && !flowWork.hasBackRelation(); + } + + /** + * 判断当前流程是否为通过流程 + */ + public boolean isPassRecord() { + return flowSourceDirection == FlowSourceDirection.PASS; + } + + /** + * 判断当前流程是否为自定义的驳回流程 + */ + public boolean isCustomBackRecord() { + return flowSourceDirection == FlowSourceDirection.REJECT && flowWork.hasBackRelation(); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java new file mode 100644 index 00000000..bcc58942 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowNodeService.java @@ -0,0 +1,364 @@ +package com.codingapi.springboot.flow.service; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowRelation; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.error.ErrorResult; +import com.codingapi.springboot.flow.error.NodeResult; +import com.codingapi.springboot.flow.error.OperatorResult; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 流程节点服务 + */ +public class FlowNodeService { + + @Getter + private FlowNode nextNode; + private IFlowOperator nextOperator; + private IFlowOperator backOperator; + + private final FlowOperatorRepository flowOperatorRepository; + private final FlowRecordRepository flowRecordRepository; + + private final String processId; + private final long preId; + + private final FlowWork flowWork; + private final FlowRecord flowRecord; + private final Opinion opinion; + private final IFlowOperator currentOperator; + private final BindDataSnapshot snapshot; + private final List historyRecords; + private final IFlowOperator createOperator; + + + public FlowNodeService(FlowOperatorRepository flowOperatorRepository, + FlowRecordRepository flowRecordRepository, + BindDataSnapshot snapshot, + Opinion opinion, + IFlowOperator createOperator, + IFlowOperator currentOperator, + List historyRecords, + FlowWork flowWork, + FlowRecord flowRecord, + String processId, + long preId) { + + this.flowOperatorRepository = flowOperatorRepository; + this.flowRecordRepository = flowRecordRepository; + this.snapshot = snapshot; + this.opinion = opinion; + this.createOperator = createOperator; + this.currentOperator = currentOperator; + this.historyRecords = historyRecords; + this.flowWork = flowWork; + this.flowRecord = flowRecord; + this.processId = processId; + this.preId = preId; + } + + + /** + * 设置下一个节点 + */ + public void setNextNode(FlowNode nextNode) { + this.nextNode = nextNode; + this.nextOperator = currentOperator; + } + + + /** + * 加载下一个节点 + */ + public void loadNextPassNode(FlowNode currentNode) { + this.nextNode = matcherNextNode(currentNode, false); + this.nextOperator = currentOperator; + } + + /** + * 跳过传阅节点流转 + */ + public void skipCirculate() { + this.nextNode = matcherNextNode(nextNode, false); + this.nextOperator = currentOperator; + } + + /** + * 加载默认回退节点 + */ + public void loadDefaultBackNode(FlowRecord currentRecord) { + List historyRecords = + flowRecordRepository.findFlowRecordByProcessId(currentRecord.getProcessId()) + .stream() + .sorted((o1, o2) -> (int) (o2.getId() - o1.getId())) + .filter(record -> record.getId() < currentRecord.getId()) + .collect(Collectors.toList()); + + int index = 0; + while (true) { + if (index >= historyRecords.size()) { + throw new IllegalArgumentException("back node not found"); + } + FlowRecord record = historyRecords.get(index); + if (record.isDone()) { + // 是连续的回退节点时,则根据流程记录的状态来判断 + if(record.isReject()){ + boolean startRemove = false; + for(FlowRecord historyRecord: historyRecords){ + if(startRemove){ + if(historyRecord.getNodeCode().equals(currentRecord.getNodeCode())){ + continue; + } + this.nextNode = flowWork.getNodeByCode(historyRecord.getNodeCode()); + this.nextOperator = historyRecord.getCurrentOperator(); + this.backOperator = historyRecord.getCurrentOperator(); + return; + } + if(historyRecord.getNodeCode().equals(currentRecord.getNodeCode())){ + startRemove = true; + } + } + }else { + this.nextNode = flowWork.getNodeByCode(record.getNodeCode()); + this.nextOperator = record.getCurrentOperator(); + this.backOperator = record.getCurrentOperator(); + return; + } + } + index++; + } + } + + + /** + * 加载自定义回退节点 + */ + public void loadCustomBackNode(FlowNode flowNode, long parentRecordId) { + FlowNode nextNode = this.matcherNextNode(flowNode, true); + if (nextNode == null) { + throw new IllegalArgumentException("next node not found"); + } + IFlowOperator flowOperator = currentOperator; + if (nextNode.isAnyOperatorMatcher()) { + // 如果是任意人员操作时则需要指定为当时审批人员为当前审批人员 + FlowRecord preFlowRecord = flowRecordRepository.getFlowRecordById(parentRecordId); + while (preFlowRecord.isTransfer() || !preFlowRecord.getNodeCode().equals(nextNode.getCode())) { + preFlowRecord = flowRecordRepository.getFlowRecordById(preFlowRecord.getPreId()); + } + flowOperator = preFlowRecord.getCurrentOperator(); + } + this.nextNode = nextNode; + this.nextOperator = flowOperator; + this.backOperator = null; + } + + + /** + * 获取下一个节点 + * + * @return 下一个节点 + */ + private FlowNode matcherNextNode(FlowNode flowNode, boolean back) { + List relations = flowWork.getRelations().stream() + .filter(relation -> relation.sourceMatcher(flowNode.getCode())) + .filter(relation -> relation.isBack() == back) + .sorted((o1, o2) -> (o2.getOrder() - o1.getOrder())) + .collect(Collectors.toList()); + if (relations.isEmpty()) { + throw new IllegalArgumentException("relation not found"); + } + FlowSession flowSession = new FlowSession(flowRecord, flowWork, flowNode, createOperator, currentOperator, snapshot.toBindData(), opinion, historyRecords); + List flowNodes = new ArrayList<>(); + for (FlowRelation flowRelation : relations) { + FlowNode node = flowRelation.trigger(flowSession); + if (node != null) { + flowNodes.add(node); + } + } + if (flowNodes.isEmpty()) { + throw new IllegalArgumentException("next node not found"); + } + return flowNodes.get(0); + } + + + /** + * 创建流程记录 + * + * @return 流程记录 + */ + public List createRecord() { + // 创建下一节点的流程记录 + List records = this.createNextRecord(); + + // 检测流程是否为抄送节点 + while (this.nextNodeIsCirculate()) { + for (FlowRecord record : records) { + record.circulate(); + } + flowRecordRepository.save(records); + + for (FlowRecord record : records) { + IFlowOperator pushOperator = record.getCurrentOperator(); + + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_CIRCULATE, + record, + pushOperator, + flowWork, + snapshot.toBindData()), + true); + } + + this.skipCirculate(); + + records = this.createNextRecord(); + } + return records; + } + + + /** + * 加载下一节点的操作者 + * + * @return 操作者 + */ + public List loadNextNodeOperators() { + FlowSession flowSession = new FlowSession(flowRecord, flowWork, nextNode, createOperator, nextOperator, snapshot.toBindData(), opinion, historyRecords); + List operators = nextNode.loadFlowNodeOperator(flowSession, flowOperatorRepository); + if (operators.isEmpty()) { + if (nextNode.hasErrTrigger()) { + ErrorResult errorResult = nextNode.errMatcher(flowSession); + if (errorResult == null) { + throw new IllegalArgumentException("errMatcher match error."); + } + if (errorResult.isOperator()) { + List operatorIds = ((OperatorResult) errorResult).getOperatorIds(); + operators = flowOperatorRepository.findByIds(operatorIds); + } + } + } + return operators; + } + + + private List createNextRecord() { + FlowSession flowSession = new FlowSession(flowRecord, flowWork, + nextNode, + createOperator, + nextOperator, + snapshot.toBindData(), + opinion, + historyRecords); + + long workId = flowWork.getId(); + List operators = null; + if (this.backOperator == null) { + operators = nextNode.loadFlowNodeOperator(flowSession, flowOperatorRepository); + } else { + operators = Collections.singletonList(this.backOperator); + } + List customOperatorIds = opinion.getOperatorIds(); + if (customOperatorIds != null && !customOperatorIds.isEmpty()) { + operators = operators.stream() + .filter(operator -> customOperatorIds.contains(operator.getUserId())).collect(Collectors.toList()); + } + List recordList; + if (operators.isEmpty()) { + recordList = this.errMatcher(nextNode, nextOperator); + if (recordList.isEmpty()) { + throw new IllegalArgumentException("operator not match."); + } + } else { + String recordTitle = nextNode.generateTitle(flowSession); + recordList = new ArrayList<>(); + for (IFlowOperator operator : operators) { + FlowRecord record = nextNode.createRecord(workId, flowWork.getCode(), processId, preId, recordTitle, createOperator, operator, snapshot, opinion.isWaiting()); + recordList.add(record); + } + } + return recordList; + } + + /** + * 异常匹配 + * + * @param currentNode 当前节点 + * @param currentOperator 当前操作者 + * @return 流程记录 + */ + private List errMatcher(FlowNode currentNode, IFlowOperator currentOperator) { + if (currentNode.hasErrTrigger()) { + FlowSession flowSession = new FlowSession(flowRecord, flowWork, currentNode, createOperator, currentOperator, snapshot.toBindData(), opinion, historyRecords); + ErrorResult errorResult = currentNode.errMatcher(flowSession); + if (errorResult == null) { + throw new IllegalArgumentException("errMatcher match error."); + } + + // 匹配操作者 + if (errorResult.isOperator()) { + List recordList = new ArrayList<>(); + List operatorIds = ((OperatorResult) errorResult).getOperatorIds(); + List operators = flowOperatorRepository.findByIds(operatorIds); + for (IFlowOperator operator : operators) { + FlowSession content = new FlowSession(flowRecord, flowWork, currentNode, createOperator, operator, snapshot.toBindData(), opinion, historyRecords); + String recordTitle = currentNode.generateTitle(content); + FlowRecord record = currentNode.createRecord(flowWork.getId(), flowWork.getCode(), processId, preId, recordTitle, createOperator, operator, snapshot, opinion.isWaiting()); + recordList.add(record); + } + return recordList; + } + // 匹配节点 + if (errorResult.isNode()) { + String nodeCode = ((NodeResult) errorResult).getNode(); + FlowNode node = flowWork.getNodeByCode(nodeCode); + if (node == null) { + throw new IllegalArgumentException("node not found."); + } + List recordList = new ArrayList<>(); + FlowSession content = new FlowSession(flowRecord, flowWork, node, createOperator, currentOperator, snapshot.toBindData(), opinion, historyRecords); + List matcherOperators = node.loadFlowNodeOperator(content, flowOperatorRepository); + if (!matcherOperators.isEmpty()) { + for (IFlowOperator matcherOperator : matcherOperators) { + String recordTitle = node.generateTitle(content); + FlowRecord record = node.createRecord(flowWork.getId(), flowWork.getCode(), processId, preId, recordTitle, createOperator, matcherOperator, snapshot, opinion.isWaiting()); + recordList.add(record); + } + } + return recordList; + } + throw new IllegalArgumentException("errMatcher not match."); + } + throw new IllegalArgumentException("operator not match."); + } + + + /** + * 下一节点的类型 + */ + public boolean nextNodeIsCirculate() { + return nextNode.isCirculate(); + } + + + /** + * 下一节点是否结束节点 + */ + public boolean nextNodeIsOver() { + return nextNode.isOverNode(); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowRecordVerifyService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowRecordVerifyService.java new file mode 100644 index 00000000..003db90c --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowRecordVerifyService.java @@ -0,0 +1,209 @@ +package com.codingapi.springboot.flow.service; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.Getter; + +import java.util.List; + +/** + * 流程记录服务(流程内部服务) + */ +public class FlowRecordVerifyService { + + // constructor params + @Getter + private final IFlowOperator currentOperator; + + // register repository + final FlowRecordRepository flowRecordRepository; + final FlowProcessRepository flowProcessRepository; + final FlowWorkRepository flowWorkRepository; + + // load Object + @Getter + private FlowWork flowWork; + @Getter + private FlowNode flowNode; + @Getter + private final FlowRecord flowRecord; + + public FlowRecordVerifyService( + FlowWorkRepository flowWorkRepository, + FlowRecordRepository flowRecordRepository, + FlowProcessRepository flowProcessRepository, + long recordId, + IFlowOperator currentOperator) { + this.flowWorkRepository = flowWorkRepository; + this.flowRecordRepository = flowRecordRepository; + this.flowProcessRepository = flowProcessRepository; + + this.currentOperator = currentOperator; + FlowRecord flowRecord = flowRecordRepository.getFlowRecordById(recordId); + if (flowRecord == null) { + throw new IllegalArgumentException("flow record not found"); + } + this.flowRecord = flowRecord; + } + + public FlowRecordVerifyService(FlowWorkRepository flowWorkRepository, + FlowRecordRepository flowRecordRepository, + FlowProcessRepository flowProcessRepository, + FlowRecord flowRecord, + FlowWork flowWork, + IFlowOperator currentOperator) { + this.flowWorkRepository = flowWorkRepository; + this.flowRecordRepository = flowRecordRepository; + this.flowProcessRepository = flowProcessRepository; + + this.currentOperator = currentOperator; + this.flowRecord = flowRecord; + this.flowWork = flowWork; + } + + + /** + * 校验流程记录是否已提交状态 + */ + public void verifyFlowRecordSubmitState() { + flowRecord.submitStateVerify(); + } + + /** + * 校验流程是否当前操作者可操作的 + */ + public void verifyFlowRecordCurrentOperator() { + if (!currentOperator.isFlowManager()) { + flowRecord.matcherOperator(currentOperator); + } + } + + /** + * 校验流程是否已审批 + */ + public void verifyFlowRecordNotDone() { + if (flowRecord.isDone()) { + throw new IllegalArgumentException("flow record is done"); + } + } + + + /** + * 校验流程是否已审批 + */ + public void verifyFlowRecordIsDone() { + if (!flowRecord.isDone()) { + throw new IllegalArgumentException("flow record is not done"); + } + } + + + /** + * 校验流程是否未审批 + */ + public void verifyFlowRecordNotTodo() { + if (flowRecord.isTodo()) { + if (!flowRecord.isStartRecord()) { + throw new IllegalArgumentException("flow record is todo"); + } + } + } + + /** + * 校验流程是未审批 + */ + public void verifyFlowRecordIsTodo() { + if (!flowRecord.isTodo()) { + throw new IllegalArgumentException("flow record is not todo"); + } + } + + /** + * 校验流程是否已完成 + */ + public void verifyFlowRecordNotFinish() { + if (flowRecord.isFinish()) { + throw new IllegalArgumentException("flow record is finish"); + } + } + + /** + * 校验流程节点是否可编辑 + */ + public void verifyFlowNodeEditableState(boolean editable) { + // 流程节点不可编辑时,不能保存 + if (flowNode.isEditable() == editable) { + throw new IllegalArgumentException("flow node is not editable"); + } + } + + + /** + * 校验转办人员不能是当前操作者 + */ + public void verifyTargetOperatorIsNotCurrentOperator(IFlowOperator targetOperator) { + if (currentOperator.getUserId() == targetOperator.getUserId()) { + throw new IllegalArgumentException("current operator is target operator"); + } + } + + + /** + * 获取流程设计对象 + */ + public void loadFlowWork() { + if (this.flowWork == null) { + FlowWork flowWork = flowProcessRepository.getFlowWorkByProcessId(flowRecord.getProcessId()); + if (flowWork == null) { + flowWork = flowWorkRepository.getFlowWorkByCode(flowRecord.getWorkCode()); + } + if (flowWork == null) { + throw new IllegalArgumentException("flow work not found"); + } + flowWork.enableValidate(); + this.flowWork = flowWork; + } + } + + + /** + * 获取流程节点对象 + */ + public void loadFlowNode() { + FlowNode flowNode = flowWork.getNodeByCode(flowRecord.getNodeCode()); + if (flowNode == null) { + throw new IllegalArgumentException("flow node not found"); + } + this.flowNode = flowNode; + } + + /** + * 标记流程为已读状态 + */ + public void setFlowRecordRead() { + if (currentOperator != null) { + if (flowRecord.isOperator(currentOperator)) { + if (!flowRecord.isRead()) { + flowRecord.read(); + flowRecordRepository.update(flowRecord); + } + } + } + } + + /** + * 校验是否后续没有审批记录 + */ + public void verifyChildrenRecordsIsEmpty() { + List childrenRecords = flowRecordRepository.findFlowRecordByPreId(flowRecord.getId()); + if (!childrenRecords.isEmpty()) { + throw new IllegalArgumentException("flow node is done"); + } + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowService.java new file mode 100644 index 00000000..0aaa054c --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowService.java @@ -0,0 +1,276 @@ +package com.codingapi.springboot.flow.service; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.pojo.FlowResult; +import com.codingapi.springboot.flow.pojo.FlowStepResult; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.result.MessageResult; +import com.codingapi.springboot.flow.service.impl.*; +import com.codingapi.springboot.flow.user.IFlowOperator; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + + +/** + * 流程服务 + */ +@Transactional +public class FlowService { + + private final FlowDetailService flowDetailService; + private final FlowCustomEventService flowCustomEventService; + private final FlowRecallService flowRecallService; + private final FlowRemoveService flowRemoveService; + private final FlowSaveService flowSaveService; + private final FlowTransferService flowTransferService; + private final FlowPostponedService flowPostponedService; + private final FlowUrgeService flowUrgeService; + + private final FlowServiceRepositoryHolder flowServiceRepositoryHolder; + + + public FlowService(FlowWorkRepository flowWorkRepository, + FlowRecordRepository flowRecordRepository, + FlowBindDataRepository flowBindDataRepository, + FlowOperatorRepository flowOperatorRepository, + FlowProcessRepository flowProcessRepository, + FlowBackupRepository flowBackupRepository) { + this.flowServiceRepositoryHolder = new FlowServiceRepositoryHolder(flowWorkRepository, flowRecordRepository, flowBindDataRepository, flowOperatorRepository, flowProcessRepository, flowBackupRepository); + this.flowDetailService = new FlowDetailService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, flowOperatorRepository, flowProcessRepository); + this.flowCustomEventService = new FlowCustomEventService(flowWorkRepository,flowRecordRepository, flowProcessRepository); + this.flowRecallService = new FlowRecallService(flowWorkRepository,flowRecordRepository, flowProcessRepository); + this.flowRemoveService = new FlowRemoveService(flowWorkRepository,flowRecordRepository, flowProcessRepository); + this.flowSaveService = new FlowSaveService(flowWorkRepository,flowRecordRepository, flowBindDataRepository, flowProcessRepository); + this.flowTransferService = new FlowTransferService(flowWorkRepository,flowRecordRepository, flowBindDataRepository, flowProcessRepository); + this.flowPostponedService = new FlowPostponedService(flowWorkRepository,flowRecordRepository, flowProcessRepository); + this.flowUrgeService = new FlowUrgeService(flowWorkRepository,flowRecordRepository, flowProcessRepository); + } + + /** + * 流程详情 + * + * @param recordId 流程记录id + * @param workCode 流程编码 + * @return 流程详情 + */ + public FlowDetail detail(long recordId, String workCode, IFlowOperator currentOperator) { + if (StringUtils.hasText(workCode)) { + return flowDetailService.detail(workCode, currentOperator); + } else { + return flowDetailService.detail(recordId, currentOperator); + } + } + + /** + * 流程详情 + * + * @param recordId 流程记录id + * @return 流程详情 + */ + public FlowDetail detail(long recordId, IFlowOperator currentOperator) { + return this.detail(recordId, null, currentOperator); + } + + + /** + * 流程详情 + * + * @param workCode 流程编号 + * @return 流程详情 + */ + public FlowDetail detail(String workCode, IFlowOperator currentOperator) { + return this.detail(0, workCode, currentOperator); + } + + + /** + * 流程详情 + * + * @param recordId 流程记录id + * @return 流程详情 + */ + public FlowDetail detail(long recordId) { + return this.detail(recordId, null, null); + } + + /** + * 延期待办 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param time 延期时间 + */ + public void postponed(long recordId, IFlowOperator currentOperator, long time) { + flowPostponedService.postponed(recordId, currentOperator, time); + } + + /** + * 催办流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void urge(long recordId, IFlowOperator currentOperator) { + flowUrgeService.urge(recordId, currentOperator); + } + + + /** + * 干预流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public FlowResult interfere(long recordId, IFlowOperator currentOperator, IBindData bindData, Opinion opinion) { + if (!currentOperator.isFlowManager()) { + throw new IllegalArgumentException("current operator is not flow manager"); + } + return this.submitFlow(recordId, currentOperator, bindData, opinion); + } + + + /** + * 转办流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param targetOperator 转办操作者 + * @param bindData 绑定数据 + * @param advice 转办意见 + */ + public void transfer(long recordId, IFlowOperator currentOperator, IFlowOperator targetOperator, IBindData bindData, String advice) { + flowTransferService.transfer(recordId, currentOperator, targetOperator, bindData, advice); + } + + + /** + * 保存流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param advice 审批意见 + */ + public void save(long recordId, IFlowOperator currentOperator, IBindData bindData, String advice) { + flowSaveService.save(recordId, currentOperator, bindData, advice); + } + + + /** + * 发起流程 (不自动提交到下一节点) + * + * @param workCode 流程编码 + * @param operator 操作者 + * @param bindData 绑定数据 + * @param advice 审批意见 + */ + public FlowResult startFlow(String workCode, IFlowOperator operator, IBindData bindData, String advice) { + FlowStartService flowStartService = new FlowStartService(workCode, operator, bindData, advice, flowServiceRepositoryHolder); + return flowStartService.startFlow(); + } + + + /** + * 尝试提交流程 (流程过程中) + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public FlowSubmitResult trySubmitFlow(long recordId, IFlowOperator currentOperator, IBindData bindData, Opinion opinion) { + FlowTrySubmitService flowTrySubmitService = new FlowTrySubmitService(currentOperator, bindData, opinion, flowServiceRepositoryHolder); + return flowTrySubmitService.trySubmitFlow(recordId); + } + + + /** + * 获取流程执行节点 + * + * @param workCode + * @param currentOperator + * @return + */ + public FlowStepResult getFlowStep(String workCode, IBindData bindData, IFlowOperator currentOperator) { + FlowStepService flowStepService = new FlowStepService(workCode, currentOperator, bindData, flowServiceRepositoryHolder); + return flowStepService.getFlowStep(); + } + + /** + * 尝试提交流程 (发起流程) + * + * @param workCode 流程编码 + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public FlowSubmitResult trySubmitFlow(String workCode, IFlowOperator currentOperator, IBindData bindData, Opinion opinion) { + FlowTrySubmitService flowTrySubmitService = new FlowTrySubmitService(currentOperator, bindData, opinion, flowServiceRepositoryHolder); + return flowTrySubmitService.trySubmitFlow(workCode); + } + + + /** + * 提交流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public FlowResult submitFlow(long recordId, IFlowOperator currentOperator, IBindData bindData, Opinion opinion) { + FlowSubmitService flowSubmitService = new FlowSubmitService(recordId, currentOperator, bindData, opinion, flowServiceRepositoryHolder); + return flowSubmitService.submitFlow(); + } + + /** + * 唤醒流程 + * @param processId 流程实例id + * @param currentOperator 当前操作者 + */ + public void notifyFlow(String processId,IFlowOperator currentOperator) { + FlowNotifyService flowNotifyService = new FlowNotifyService(processId, currentOperator, flowServiceRepositoryHolder); + flowNotifyService.notifyFlow(); + } + + /** + * 自定义事件 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param buttonId 按钮id + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public MessageResult customFlowEvent(long recordId, IFlowOperator currentOperator, String buttonId, IBindData bindData, Opinion opinion) { + return flowCustomEventService.customFlowEvent(recordId, currentOperator, buttonId, bindData, opinion); + } + + + /** + * 撤回流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void recall(long recordId, IFlowOperator currentOperator) { + flowRecallService.recall(recordId, currentOperator); + } + + + + /** + * 删除流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void remove(long recordId, IFlowOperator currentOperator) { + flowRemoveService.remove(recordId, currentOperator); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowServiceRepositoryHolder.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowServiceRepositoryHolder.java new file mode 100644 index 00000000..20b8c8b6 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowServiceRepositoryHolder.java @@ -0,0 +1,29 @@ +package com.codingapi.springboot.flow.service; + +import com.codingapi.springboot.flow.repository.*; +import lombok.Getter; + +@Getter +public class FlowServiceRepositoryHolder { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowBindDataRepository flowBindDataRepository; + private final FlowOperatorRepository flowOperatorRepository; + private final FlowProcessRepository flowProcessRepository; + private final FlowBackupRepository flowBackupRepository; + + public FlowServiceRepositoryHolder(FlowWorkRepository flowWorkRepository, + FlowRecordRepository flowRecordRepository, + FlowBindDataRepository flowBindDataRepository, + FlowOperatorRepository flowOperatorRepository, + FlowProcessRepository flowProcessRepository, + FlowBackupRepository flowBackupRepository){ + this.flowWorkRepository = flowWorkRepository; + this.flowRecordRepository = flowRecordRepository; + this.flowBindDataRepository = flowBindDataRepository; + this.flowOperatorRepository = flowOperatorRepository; + this.flowProcessRepository = flowProcessRepository; + this.flowBackupRepository = flowBackupRepository; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowCustomEventService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowCustomEventService.java new file mode 100644 index 00000000..67fbbbd8 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowCustomEventService.java @@ -0,0 +1,76 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowButton; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.result.MessageResult; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Transactional +@AllArgsConstructor +public class FlowCustomEventService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowProcessRepository flowProcessRepository; + + /** + * 自定义事件 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param buttonId 按钮id + * @param bindData 绑定数据 + * @param opinion 审批意见 + */ + public MessageResult customFlowEvent(long recordId, IFlowOperator currentOperator, String buttonId, IBindData bindData, Opinion opinion) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService(flowWorkRepository,flowRecordRepository, flowProcessRepository, recordId, currentOperator); + + // 验证流程的提交状态 + flowRecordVerifyService.verifyFlowRecordSubmitState(); + // 验证当前操作者 + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + // 加载流程设计 + flowRecordVerifyService.loadFlowWork(); + // 加载流程节点 + flowRecordVerifyService.loadFlowNode(); + // 验证没有子流程 + flowRecordVerifyService.verifyChildrenRecordsIsEmpty(); + + // 获取流程记录对象 + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowNode flowNode = flowRecordVerifyService.getFlowNode(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + // 与当前流程同级的流程记录 + List historyRecords; + if (flowRecord.isStartRecord()) { + historyRecords = new ArrayList<>(); + } else { + historyRecords = flowRecordRepository.findFlowRecordByPreId(flowRecord.getPreId()); + } + + // 获取流程的发起者 + IFlowOperator createOperator = flowRecord.getCreateOperator(); + FlowButton flowButton = flowNode.getButton(buttonId); + if (flowButton == null) { + throw new IllegalArgumentException("flow button not found"); + } + if (!flowButton.hasGroovy()) { + throw new IllegalArgumentException("flow button not groovy"); + } + return flowButton.run(flowRecord, flowNode, flowWork, createOperator, currentOperator, bindData, opinion, historyRecords); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java new file mode 100644 index 00000000..718784d8 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowDetailService.java @@ -0,0 +1,124 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowMerge; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +@AllArgsConstructor +public class FlowDetailService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowBindDataRepository flowBindDataRepository; + private final FlowOperatorRepository flowOperatorRepository; + private final FlowProcessRepository flowProcessRepository; + + + /** + * 流程详情 + * 如果传递了currentOperator为流程的审批者时,在查看详情的时候可以将流程记录标记为已读 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public FlowDetail detail(long recordId, IFlowOperator currentOperator) { + + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService(flowWorkRepository,flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + + flowRecordVerifyService.setFlowRecordRead(); + flowRecordVerifyService.loadFlowWork(); + + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + List mergeRecords = null; + if(flowRecord.isTodo() && flowRecord.isMergeable()){ + List flowRecords = flowRecordRepository.findMergeFlowRecordById(flowRecord.getWorkCode(),flowRecord.getNodeCode(),currentOperator.getUserId()); + if(!flowRecords.isEmpty()){ + mergeRecords = flowRecords.stream().map(record->{ + BindDataSnapshot bindDataSnapshot = flowBindDataRepository.getBindDataSnapshotById(record.getSnapshotId()); + return new FlowMerge(record,bindDataSnapshot.toBindData()); + }).collect(Collectors.toList()); + } + } + + BindDataSnapshot snapshot = flowBindDataRepository.getBindDataSnapshotById(flowRecord.getSnapshotId()); + List flowRecords = + flowRecordRepository.findFlowRecordByProcessId(flowRecord.getProcessId()). + stream(). + sorted((o1, o2) -> (int) (o2.getId() - o1.getId())) + .collect(Collectors.toList()); + + List operators = new ArrayList<>(); + // 获取所有的操作者 + for (FlowRecord record : flowRecords) { + operators.add(record.getCreateOperator()); + operators.add(record.getCurrentOperator()); + if (record.getInterferedOperator() != null) { + operators.add(record.getInterferedOperator()); + } + } + + return new FlowDetail(flowRecord,mergeRecords, snapshot, flowWork, flowRecords, operators, currentOperator != null && flowRecord.isTodo() && flowRecord.isOperator(currentOperator)); + } + + + /** + * 发起流程详情 + * 如果传递了currentOperator为流程的审批者时,在查看详情的时候可以将流程记录标记为已读 + * + * @param workCode 流程记录id + * @param currentOperator 当前操作者 + */ + public FlowDetail detail(String workCode, IFlowOperator currentOperator) { + + if (currentOperator == null) { + throw new IllegalArgumentException("current operator is null"); + } + + FlowWork flowWork = flowWorkRepository.getFlowWorkByCode(workCode); + if (flowWork == null) { + throw new IllegalArgumentException("flow work not found"); + } + flowWork.enableValidate(); + + // 获取开始节点 + FlowNode flowNode = flowWork.getStartNode(); + + FlowSession flowSession = new FlowSession( + null, + flowWork, + flowNode, + currentOperator, + currentOperator, + null, + null, + new ArrayList<>()); + + List operators = flowNode.loadFlowNodeOperator(flowSession, flowOperatorRepository); + + List operatorIds = operators.stream().map(IFlowOperator::getUserId).collect(Collectors.toList()); + if (!operatorIds.contains(currentOperator.getUserId())) { + throw new IllegalArgumentException("current operator is not flow operator"); + } + + return new FlowDetail(flowWork, flowNode, operators,true); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowNotifyService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowNotifyService.java new file mode 100644 index 00000000..53d11750 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowNotifyService.java @@ -0,0 +1,81 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.em.FlowType; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowBindDataRepository; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowServiceRepositoryHolder; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +public class FlowNotifyService { + + private final String processId; + private final IFlowOperator currentOperator; + private final FlowRecordRepository flowRecordRepository; + private final FlowBindDataRepository flowBindDataRepository; + private final FlowWorkRepository flowWorkRepository; + private final FlowProcessRepository flowProcessRepository; + + + public FlowNotifyService(String processId, IFlowOperator currentOperator, FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.processId = processId; + this.currentOperator = currentOperator; + this.flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + this.flowBindDataRepository = flowServiceRepositoryHolder.getFlowBindDataRepository(); + this.flowWorkRepository = flowServiceRepositoryHolder.getFlowWorkRepository(); + this.flowProcessRepository = flowServiceRepositoryHolder.getFlowProcessRepository(); + } + + + /** + * 获取流程设计对象 + */ + public FlowWork loadFlowWork(FlowRecord flowRecord) { + FlowWork flowWork = flowProcessRepository.getFlowWorkByProcessId(flowRecord.getProcessId()); + if (flowWork == null) { + flowWork = flowWorkRepository.getFlowWorkByCode(flowRecord.getWorkCode()); + } + if (flowWork == null) { + throw new IllegalArgumentException("flow work not found"); + } + flowWork.enableValidate(); + + return flowWork; + } + + /** + * 流程通知 + */ + public void notifyFlow() { + List flowRecords = flowRecordRepository.findFlowRecordByProcessId(processId); + List waitingRecords = flowRecords.stream().filter(FlowRecord::isWaiting).collect(Collectors.toList()); + for (FlowRecord flowRecord : waitingRecords) { + if (flowRecord.isOperator(currentOperator)) { + flowRecord.setFlowType(FlowType.TODO); + flowRecordRepository.update(flowRecord); + + BindDataSnapshot snapshot = flowBindDataRepository.getBindDataSnapshotById(flowRecord.getSnapshotId()); + + FlowWork flowWork = this.loadFlowWork(flowRecord); + + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_TODO, + flowRecord, + flowRecord.getCurrentOperator(), + flowWork, + snapshot.toBindData() + ), true); + } + } + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowPostponedService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowPostponedService.java new file mode 100644 index 00000000..68cbbc0f --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowPostponedService.java @@ -0,0 +1,45 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@AllArgsConstructor +public class FlowPostponedService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowProcessRepository flowProcessRepository; + + /** + * 延期待办 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param time 延期时间 + */ + public void postponed(long recordId, IFlowOperator currentOperator, long time) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService(flowWorkRepository,flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + + flowRecordVerifyService.verifyFlowRecordSubmitState(); + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.verifyFlowRecordNotFinish(); + flowRecordVerifyService.verifyFlowRecordNotDone(); + + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + flowRecord.postponedTime(flowWork.getPostponedMax(), time); + flowRecordRepository.update(flowRecord); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRecallService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRecallService.java new file mode 100644 index 00000000..124f48b8 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRecallService.java @@ -0,0 +1,76 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +@Transactional +@AllArgsConstructor +public class FlowRecallService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowProcessRepository flowProcessRepository; + + /** + * 撤回流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void recall(long recordId, IFlowOperator currentOperator) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService( + flowWorkRepository, + flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.loadFlowNode(); + flowRecordVerifyService.verifyFlowRecordNotFinish(); + flowRecordVerifyService.verifyFlowRecordNotTodo(); + + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + // 下一流程的流程记录 + List childrenRecords = flowRecordRepository.findFlowRecordByPreId(recordId); + // 下一流程均为办理且未读 + + // 如果是在开始节点撤销,则直接删除 + if (flowRecord.isStartRecord() && flowRecord.isTodo()) { + if (!childrenRecords.isEmpty()) { + throw new IllegalArgumentException("flow record not recall"); + } + flowRecordRepository.delete(Collections.singletonList(flowRecord)); + } else { + // 如果是在中间节点撤销,则需要判断是否所有的子流程都是未读状态 + if (childrenRecords.isEmpty()) { + throw new IllegalArgumentException("flow record not submit"); + } + + boolean allUnDone = childrenRecords.stream().allMatch(item -> item.isUnRead() && item.isTodo()); + if (!allUnDone) { + throw new IllegalArgumentException("flow record not recall"); + } + flowRecord.recall(); + flowRecordRepository.update(flowRecord); + + flowRecordRepository.delete(childrenRecords); + } + + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_RECALL, flowRecord, currentOperator, flowWork, null), true); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRemoveService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRemoveService.java new file mode 100644 index 00000000..82c42198 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowRemoveService.java @@ -0,0 +1,58 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +@Transactional +@AllArgsConstructor +public class FlowRemoveService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowProcessRepository flowProcessRepository; + + + /** + * 删除流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void remove(long recordId, IFlowOperator currentOperator) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService( + flowWorkRepository, + flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + + + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.loadFlowNode(); + flowRecordVerifyService.verifyFlowRecordNotFinish(); + flowRecordVerifyService.verifyFlowRecordIsTodo(); + FlowNode flowNode = flowRecordVerifyService.getFlowNode(); + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + + if(!flowNode.isStartNode()){ + throw new IllegalArgumentException("flow record not remove"); + } + + flowProcessRepository.deleteByProcessId(flowRecord.getProcessId()); + + flowRecordRepository.deleteByProcessId(flowRecord.getProcessId()); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java new file mode 100644 index 00000000..f73c6550 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSaveService.java @@ -0,0 +1,65 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowBindDataRepository; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@AllArgsConstructor +public class FlowSaveService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowBindDataRepository flowBindDataRepository; + private final FlowProcessRepository flowProcessRepository; + + /** + * 保存流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param bindData 绑定数据 + * @param advice 审批意见 + */ + public void save(long recordId, IFlowOperator currentOperator, IBindData bindData, String advice) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService(flowWorkRepository, + flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + flowRecordVerifyService.verifyFlowRecordSubmitState(); + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.loadFlowNode(); + flowRecordVerifyService.verifyFlowNodeEditableState(false); + + Opinion opinion = Opinion.save(advice); + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + BindDataSnapshot snapshot = new BindDataSnapshot(flowRecord.getSnapshotId(), bindData); + flowBindDataRepository.update(snapshot); + + flowRecord.setOpinion(opinion); + flowRecordRepository.update(flowRecord); + + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_SAVE, + flowRecord, + flowRecord.getCurrentOperator(), + flowWork, + snapshot.toBindData()), + true); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java new file mode 100644 index 00000000..d035a5e4 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStartService.java @@ -0,0 +1,236 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.FlowSourceDirection; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.pojo.FlowResult; +import com.codingapi.springboot.flow.record.FlowBackup; +import com.codingapi.springboot.flow.record.FlowProcess; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowBackupRepository; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowNodeService; +import com.codingapi.springboot.flow.service.FlowServiceRepositoryHolder; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.Getter; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Transactional +public class FlowStartService { + + private final String workCode; + private final IFlowOperator operator; + private final IBindData bindData; + private final Opinion opinion; + private final FlowServiceRepositoryHolder flowServiceRepositoryHolder; + + + @Getter + private FlowWork flowWork; + private FlowNode flowNode; + private FlowBackup flowBackup; + private FlowProcess flowProcess; + private BindDataSnapshot snapshot; + private FlowNodeService flowNodeService; + + public FlowStartService(String workCode, + IFlowOperator operator, + IBindData bindData, + String advice, + FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.workCode = workCode; + this.operator = operator; + this.bindData = bindData; + this.opinion = Opinion.pass(advice); + this.flowServiceRepositoryHolder = flowServiceRepositoryHolder; + } + + private void loadFlowWork() { + // 检测流程是否存在 + FlowWorkRepository flowWorkRepository = flowServiceRepositoryHolder.getFlowWorkRepository(); + this.flowWork = flowWorkRepository.getFlowWorkByCode(workCode); + if (flowWork == null) { + throw new IllegalArgumentException("flow work not found"); + } + flowWork.verify(); + flowWork.enableValidate(); + } + + private void loadFlowBackup() { + FlowBackupRepository flowBackupRepository = flowServiceRepositoryHolder.getFlowBackupRepository(); + this.flowBackup = flowBackupRepository.getFlowBackupByWorkIdAndVersion(flowWork.getId(), flowWork.getUpdateTime()); + if (flowBackup == null) { + flowBackup = flowBackupRepository.backup(flowWork); + } + } + + private void saveFlowProcess() { + this.flowProcess = new FlowProcess(flowBackup.getId(), operator); + flowServiceRepositoryHolder.getFlowProcessRepository().save(flowProcess); + } + + private void saveBindDataSnapshot() { + snapshot = new BindDataSnapshot(bindData); + flowServiceRepositoryHolder.getFlowBindDataRepository().save(snapshot); + } + + private void buildFlowNodeService() { + + // 获取开始节点 + FlowNode start = flowWork.getStartNode(); + if (start == null) { + throw new IllegalArgumentException("start node not found"); + } + + this.flowNode = start; + // 设置开始流程的上一个流程id + long preId = 0; + + // 创建流程id + String processId = flowProcess.getProcessId(); + + List historyRecords = new ArrayList<>(); + + FlowOperatorRepository flowOperatorRepository = flowServiceRepositoryHolder.getFlowOperatorRepository(); + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + + flowNodeService = new FlowNodeService(flowOperatorRepository, + flowRecordRepository, + snapshot, + opinion, + operator, + operator, + historyRecords, + flowWork, + null, + processId, + preId); + + flowNodeService.setNextNode(start); + } + + + private void pushEvent(int flowApprovalEventState, FlowRecord flowRecord) { + EventPusher.push(new FlowApprovalEvent(flowApprovalEventState, + flowRecord, + flowRecord.getCurrentOperator(), + flowWork, + snapshot.toBindData()), + true); + } + + + private void saveFlowRecords(List flowRecords) { + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + flowRecordRepository.save(flowRecords); + } + + + /** + * 发起流程 (不自动提交到下一节点) + */ + public FlowResult startFlow() { + // 检测流程是否存在 + this.loadFlowWork(); + + // 流程数据备份 + this.loadFlowBackup(); + + // 保存流程 + this.saveFlowProcess(); + + // 保存绑定数据 + this.saveBindDataSnapshot(); + + // 构建流程节点服务 + this.buildFlowNodeService(); + + // 创建待办记录 + List records = flowNodeService.createRecord(); + if (records.isEmpty()) { + throw new IllegalArgumentException("flow record not found"); + } else { + for (FlowRecord record : records) { + record.updateOpinion(opinion); + } + } + + // 检测流程是否结束 + if (flowNodeService.nextNodeIsOver()) { + for (FlowRecord record : records) { + record.submitRecord(operator, snapshot, opinion, FlowSourceDirection.PASS); + record.finish(); + } + + this.saveFlowRecords(records); + + // 推送事件 + for (FlowRecord record : records) { + this.pushEvent(FlowApprovalEvent.STATE_CREATE, record); + this.pushEvent(FlowApprovalEvent.STATE_FINISH, record); + } + return new FlowResult(flowWork, records); + } + + // 保存流程记录 + this.saveFlowRecords(records); + + // 推送事件消息 + for (FlowRecord record : records) { + this.pushEvent(FlowApprovalEvent.STATE_CREATE, record); + this.pushEvent(FlowApprovalEvent.STATE_TODO, record); + this.pushEvent(FlowApprovalEvent.STATE_SAVE, record); + } + // 当前的审批记录 + return new FlowResult(flowWork, records); + } + + + public FlowRecord tryStartFlow() { + // 检测流程是否存在 + this.loadFlowWork(); + // 流程数据备份 + this.loadFlowBackup(); + + // 保存绑定数据 + snapshot = new BindDataSnapshot(bindData); + // 保存流程 + flowProcess = new FlowProcess(flowBackup.getId(), operator); + + // 构建流程节点服务 + this.buildFlowNodeService(); + + FlowRecord startRecord = null; + + // 创建待办记录 + List records = flowNodeService.createRecord(); + if (records.isEmpty()) { + throw new IllegalArgumentException("flow record not found"); + } else { + for (FlowRecord record : records) { + record.updateOpinion(opinion); + startRecord = record; + } + } + + // 检测流程是否结束 + if (flowNodeService.nextNodeIsOver()) { + for (FlowRecord record : records) { + record.submitRecord(operator, snapshot, opinion, FlowSourceDirection.PASS); + record.finish(); + startRecord = record; + } + } + return startRecord; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStepService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStepService.java new file mode 100644 index 00000000..c03833dd --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowStepService.java @@ -0,0 +1,83 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.pojo.FlowStepResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowOperatorRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.service.FlowNodeService; +import com.codingapi.springboot.flow.service.FlowServiceRepositoryHolder; +import com.codingapi.springboot.flow.user.IFlowOperator; + +import java.util.ArrayList; +import java.util.List; + +public class FlowStepService { + private final FlowWork flowWork; + + private final IFlowOperator currentOperator; + private final IBindData bindData; + private final FlowServiceRepositoryHolder flowServiceRepositoryHolder; + + private FlowNodeService flowNodeService; + private FlowNode flowNode; + + public FlowStepService(String workCode, IFlowOperator currentOperator, IBindData bindData, FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.currentOperator = currentOperator; + this.bindData = bindData; + this.flowServiceRepositoryHolder = flowServiceRepositoryHolder; + this.flowWork = flowServiceRepositoryHolder.getFlowWorkRepository().getFlowWorkByCode(workCode); + } + + + public FlowStepResult getFlowStep() { + FlowStepResult flowStepResult = new FlowStepResult(); + // 获取开始节点 + FlowNode start = flowWork.getStartNode(); + if (start == null) { + throw new IllegalArgumentException("start node not found"); + } + + this.flowNode = start; + // 设置开始流程的上一个流程id + long preId = 0; + + // 创建流程id + String processId = "flow_" + System.currentTimeMillis(); + + List historyRecords = new ArrayList<>(); + + FlowOperatorRepository flowOperatorRepository = flowServiceRepositoryHolder.getFlowOperatorRepository(); + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + + BindDataSnapshot snapshot = new BindDataSnapshot(bindData); + flowNodeService = new FlowNodeService(flowOperatorRepository, + flowRecordRepository, + snapshot, + Opinion.pass("同意"), + currentOperator, + currentOperator, + historyRecords, + flowWork, + null, + processId, + preId); + + flowNodeService.setNextNode(start); + + this.flowNode = start; + flowStepResult.addFlowNode(this.flowNode, this.flowNodeService.loadNextNodeOperators()); + + do { + flowNodeService.loadNextPassNode(this.flowNode); + this.flowNode = flowNodeService.getNextNode(); + flowStepResult.addFlowNode(this.flowNode, this.flowNodeService.loadNextNodeOperators()); + } while (!flowNode.isOverNode()); + + return flowStepResult; + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSubmitService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSubmitService.java new file mode 100644 index 00000000..fd188fcd --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowSubmitService.java @@ -0,0 +1,389 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.FlowSourceDirection; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.pojo.FlowResult; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowBindDataRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.service.FlowDirectionService; +import com.codingapi.springboot.flow.service.FlowNodeService; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.service.FlowServiceRepositoryHolder; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Transactional +public class FlowSubmitService { + + + private final IFlowOperator currentOperator; + private final IBindData bindData; + private final Opinion opinion; + private final FlowServiceRepositoryHolder flowServiceRepositoryHolder; + private final FlowRecordVerifyService flowRecordVerifyService; + + + private FlowRecord flowRecord; + private FlowWork flowWork; + private FlowNode flowNode; + private FlowNode nextNode; + + + private BindDataSnapshot snapshot; + private FlowNodeService flowNodeService; + private FlowDirectionService flowDirectionService; + private FlowSourceDirection flowSourceDirection; + + + public FlowSubmitService(long recordId, + IFlowOperator currentOperator, + IBindData bindData, + Opinion opinion, + FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.flowServiceRepositoryHolder = flowServiceRepositoryHolder; + this.currentOperator = currentOperator; + this.bindData = bindData; + this.opinion = opinion; + this.flowRecordVerifyService = new FlowRecordVerifyService( + flowServiceRepositoryHolder.getFlowWorkRepository(), + flowServiceRepositoryHolder.getFlowRecordRepository(), + flowServiceRepositoryHolder.getFlowProcessRepository(), + recordId, + currentOperator); + } + + + public FlowSubmitService(FlowRecord flowRecord, + FlowWork flowWork, + IFlowOperator currentOperator, + IBindData bindData, + Opinion opinion, + FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.flowWork = flowWork; + this.flowServiceRepositoryHolder = flowServiceRepositoryHolder; + this.currentOperator = currentOperator; + this.bindData = bindData; + this.opinion = opinion; + this.flowRecordVerifyService = new FlowRecordVerifyService( + flowServiceRepositoryHolder.getFlowWorkRepository(), + flowServiceRepositoryHolder.getFlowRecordRepository(), + flowServiceRepositoryHolder.getFlowProcessRepository(), + flowRecord, + flowWork, + currentOperator); + } + + + // 加载流程 + private void loadFlow(boolean testSubmit) { + // 验证流程的提交状态 + flowRecordVerifyService.verifyFlowRecordSubmitState(); + // 验证当前操作者 + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + + // 加载流程设计 + flowRecordVerifyService.loadFlowWork(); + // 加载流程节点 + flowRecordVerifyService.loadFlowNode(); + // 验证没有子流程 + flowRecordVerifyService.verifyChildrenRecordsIsEmpty(); + + if (testSubmit) { + this.flowRecord = flowRecordVerifyService.getFlowRecord().copy(); + } else { + this.flowRecord = flowRecordVerifyService.getFlowRecord(); + } + this.flowNode = flowRecordVerifyService.getFlowNode(); + this.flowWork = flowRecordVerifyService.getFlowWork(); + } + + // 保存流程表单快照数据 + private void saveSnapshot(boolean testSubmit) { + FlowBindDataRepository flowBindDataRepository = flowServiceRepositoryHolder.getFlowBindDataRepository(); + if (flowNode.isEditable()) { + snapshot = new BindDataSnapshot(bindData); + if (!testSubmit) { + flowBindDataRepository.save(snapshot); + } + } else { + snapshot = flowBindDataRepository.getBindDataSnapshotById(flowRecord.getSnapshotId()); + } + } + + // 加载流程审批方向 + private void loadFlowDirection() { + // 审批方向判断服务 + flowDirectionService = new FlowDirectionService(flowRecordVerifyService.getFlowNode(), flowRecordVerifyService.getFlowWork(), opinion); + + // 加载流程审批方向 + flowDirectionService.loadFlowSourceDirection(); + // 验证审批方向 + flowDirectionService.verifyFlowSourceDirection(); + + flowSourceDirection = flowDirectionService.getFlowSourceDirection(); + } + + + // 与当前流程同级的流程记录 + private List loadHistoryRecords(boolean testSubmit) { + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + // 与当前流程同级的流程记录 + List historyRecords; + if (flowRecord.isStartRecord()) { + historyRecords = new ArrayList<>(); + } else { + if (testSubmit) { + // copy 流程数据防止影响原有数据 + historyRecords = flowRecordRepository.findFlowRecordByPreId(flowRecord.getPreId()).stream().map(FlowRecord::copy).collect(Collectors.toList()); + // 更新当前流程记录, 由于try测试过程中没有对数据落库,所以这里需要手动更新 + for (FlowRecord record : historyRecords) { + if (record.getId() == flowRecord.getId()) { + record.submitRecord(currentOperator, snapshot, opinion, flowSourceDirection); + } + } + + } else { + historyRecords = flowRecordRepository.findFlowRecordByPreId(flowRecord.getPreId()); + } + } + return historyRecords; + } + + + // 保存流程记录 + private void saveFlowRecord(FlowRecord flowRecord) { + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + flowRecordRepository.update(flowRecord); + } + + + // 生成下一节点的流程记录 + private void loadNextNode(List historyRecords) { + // 获取流程的发起者 + IFlowOperator createOperator = flowRecord.getCreateOperator(); + + // 构建流程创建器 + flowNodeService = new FlowNodeService( + flowServiceRepositoryHolder.getFlowOperatorRepository(), + flowServiceRepositoryHolder.getFlowRecordRepository(), + snapshot, + opinion, + createOperator, + currentOperator, + historyRecords, + flowWork, + flowRecord, + flowRecord.getProcessId(), + flowRecord.getId() + ); + + // 审批通过并进入下一节点 + if (flowDirectionService.isPassRecord()) { + flowNodeService.loadNextPassNode(flowNode); + // 审批拒绝返回上一节点 + } else if (flowDirectionService.isDefaultBackRecord()) { + flowNodeService.loadDefaultBackNode(flowRecord); + } else { + // 审批拒绝,并且自定了返回节点 + flowNodeService.loadCustomBackNode(flowNode, flowRecord.getPreId()); + } + this.nextNode = flowNodeService.getNextNode(); + } + + + // 更新流程记录 + private void updateFinishFlowRecord() { + flowServiceRepositoryHolder.getFlowRecordRepository().finishFlowRecordByProcessId(flowRecord.getProcessId()); + } + + // 保存流程记录 + private void saveNextFlowRecords(List flowRecords) { + flowServiceRepositoryHolder.getFlowRecordRepository().save(flowRecords); + } + + // 推送审批事件消息 + private void pushEvent(FlowRecord flowRecord, int eventState) { + EventPusher.push(new FlowApprovalEvent(eventState, + flowRecord, + flowRecord.getCurrentOperator(), + flowWork, + snapshot.toBindData() + ), true); + } + + + /** + * 提交流程 根据流程的是否跳过相同审批人来判断是否需要继续提交 + * + * @return 流程结果 + */ + public FlowResult submitFlow() { + FlowResult flowResult = this.submitCurrentFlow(); + if (this.isSkipIfSameApprover() && !flowResult.isOver() && !flowResult.isStart()) { + List flowRecords = flowResult.matchRecordByOperator(currentOperator); + FlowResult result = flowResult; + if (!flowRecords.isEmpty()) { + for (FlowRecord flowRecord : flowRecords) { + FlowSubmitService flowSubmitService = new FlowSubmitService(flowRecord.getId(), currentOperator, bindData, opinion, flowServiceRepositoryHolder); + result = flowSubmitService.submitFlow(); + } + } + return result; + } else { + return flowResult; + } + } + + /** + * 提交当前流程 + * + * @return 流程结果 + */ + private FlowResult submitCurrentFlow() { + // 加载流程信息 + this.loadFlow(false); + + // 保存流程表单快照数据 + this.saveSnapshot(false); + + // 审批方向判断服务 + this.loadFlowDirection(); + + // 提交流程记录 + flowRecord.submitRecord(currentOperator, snapshot, opinion, flowSourceDirection); + this.saveFlowRecord(flowRecord); + + // 与当前流程同级的流程记录 + List historyRecords = this.loadHistoryRecords(false); + flowDirectionService.bindHistoryRecords(historyRecords); + + // 判断流程是否结束(会签时需要所有人都通过) + if (flowNode.isSign()) { + boolean isDone = flowDirectionService.hasCurrentFlowNodeIsDone(); + if (!isDone) { + List todoRecords = historyRecords.stream().filter(FlowRecord::isTodo).collect(Collectors.toList()); + return new FlowResult(flowWork, todoRecords); + } + } + + // 非会签下,当有人提交以后,将所有未提交的流程都自动提交,然后再执行下一节点 + if (flowNode.isUnSign()) { + for (FlowRecord record : historyRecords) { + if (record.isTodo() && record.getId() != flowRecord.getId()) { + record.autoPass(currentOperator, snapshot); + FlowRecordRepository flowRecordRepository = flowServiceRepositoryHolder.getFlowRecordRepository(); + flowRecordRepository.update(record); + } + } + } + + // 根据所有提交意见,重新加载审批方向 + flowSourceDirection = flowDirectionService.reloadFlowSourceDirection(); + + this.loadNextNode(historyRecords); + + // 生成下一节点的流程记录 + List nextRecords = flowNodeService.createRecord(); + + // 判断流程是否完成 + if (flowNodeService.nextNodeIsOver()) { + flowRecord.submitRecord(currentOperator, snapshot, opinion, flowSourceDirection); + flowRecord.finish(); + this.saveFlowRecord(flowRecord); + this.updateFinishFlowRecord(); + + this.pushEvent(flowRecord, FlowApprovalEvent.STATE_FINISH); + + if (!nextRecords.isEmpty()) { + return new FlowResult(flowWork, nextRecords.get(0)); + } + return new FlowResult(flowWork, flowRecord); + } + + // 保存流程记录 + this.saveNextFlowRecords(nextRecords); + + // 推送审批事件消息 + int eventState = flowSourceDirection == FlowSourceDirection.PASS ? FlowApprovalEvent.STATE_PASS : FlowApprovalEvent.STATE_REJECT; + this.pushEvent(flowRecord, eventState); + + // 推送待办事件消息 + for (FlowRecord record : nextRecords) { + if(record.isTodo()) { + this.pushEvent(record, FlowApprovalEvent.STATE_TODO); + } + } + + return new FlowResult(flowWork, nextRecords); + } + + /** + * 提交流程 + **/ + public FlowSubmitResult trySubmitFlow() { + // 加载流程信息 + this.loadFlow(true); + + // 保存流程表单快照数据 + this.saveSnapshot(true); + + // 审批方向判断服务 + this.loadFlowDirection(); + + // 提交流程记录 + flowRecord.submitRecord(currentOperator, snapshot, opinion, flowSourceDirection); + + // 与当前流程同级的流程记录 + List historyRecords = this.loadHistoryRecords(true); + flowDirectionService.bindHistoryRecords(historyRecords); + + // 判断流程是否结束(会签时需要所有人都通过) + if (flowNode.isSign()) { + boolean isDone = flowDirectionService.hasCurrentFlowNodeIsDone(); + if (!isDone) { + List todoRecords = historyRecords.stream().filter(FlowRecord::isTodo).collect(Collectors.toList()); + return new FlowSubmitResult(flowWork, flowNode, todoRecords.stream().map(FlowRecord::getCurrentOperator).collect(Collectors.toList())); + } + } + + // 非会签下,当有人提交以后,将所有未提交的流程都自动提交,然后再执行下一节点 + if (flowNode.isUnSign()) { + for (FlowRecord record : historyRecords) { + if (record.isTodo() && record.getId() != flowRecord.getId()) { + record.autoPass(currentOperator, snapshot); + } + } + } + + // 根据所有提交意见,重新加载审批方向 + flowSourceDirection = flowDirectionService.reloadFlowSourceDirection(); + + this.loadNextNode(historyRecords); + + while (nextNode.isCirculate()){ + flowNodeService.skipCirculate(); + this.nextNode = flowNodeService.getNextNode(); + } + + List operators = flowNodeService.loadNextNodeOperators(); + return new FlowSubmitResult(flowWork, nextNode, operators); + } + + + // 是否跳过相同审批人 + public boolean isSkipIfSameApprover() { + return flowWork.isSkipIfSameApprover() && !nextNode.isOverNode(); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTransferService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTransferService.java new file mode 100644 index 00000000..fc6e8007 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTransferService.java @@ -0,0 +1,101 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowBindDataRepository; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Transactional +@AllArgsConstructor +public class FlowTransferService { + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowBindDataRepository flowBindDataRepository; + private final FlowProcessRepository flowProcessRepository; + + + /** + * 转办流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + * @param targetOperator 转办操作者 + * @param bindData 绑定数据 + * @param advice 转办意见 + */ + public void transfer(long recordId, IFlowOperator currentOperator, IFlowOperator targetOperator, IBindData bindData, String advice) { + + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService(flowWorkRepository, flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + + flowRecordVerifyService.verifyFlowRecordSubmitState(); + flowRecordVerifyService.verifyFlowRecordCurrentOperator(); + flowRecordVerifyService.verifyTargetOperatorIsNotCurrentOperator(targetOperator); + + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.loadFlowNode(); + + flowRecordVerifyService.verifyFlowRecordIsTodo(); + + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + FlowNode flowNode = flowRecordVerifyService.getFlowNode(); + + + // 保存绑定数据 + BindDataSnapshot snapshot = new BindDataSnapshot(bindData); + flowBindDataRepository.save(snapshot); + + // 构建审批意见 + Opinion opinion = Opinion.transfer(advice); + + // 设置自己的流程状态为转办已完成 + flowRecord.transfer(currentOperator, snapshot, opinion); + flowRecordRepository.update(flowRecord); + + // 获取创建者 + IFlowOperator createOperator = flowRecord.getCreateOperator(); + + // 与当前流程同级的流程记录 + List historyRecords; + if (flowRecord.isStartRecord()) { + historyRecords = new ArrayList<>(); + } else { + historyRecords = flowRecordRepository.findFlowRecordByPreId(flowRecord.getPreId()); + } + + // 创建新的待办标题 + FlowSession content = new FlowSession(flowRecord, flowWork, flowNode, createOperator, targetOperator, snapshot.toBindData(), opinion, historyRecords); + String generateTitle = flowNode.generateTitle(content); + + // 创建转办记录 + FlowRecord transferRecord = flowRecord.copy(); + transferRecord.transferToTodo(generateTitle, targetOperator); + flowRecordRepository.save(Collections.singletonList(transferRecord)); + + // 推送转办消息 + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_TRANSFER, flowRecord, currentOperator, flowWork, snapshot.toBindData()), true); + + // 推送待办消息 + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_TODO, transferRecord, targetOperator, flowWork, snapshot.toBindData()), true); + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTrySubmitService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTrySubmitService.java new file mode 100644 index 00000000..a136f492 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowTrySubmitService.java @@ -0,0 +1,44 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.bind.IBindData; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.service.FlowServiceRepositoryHolder; +import com.codingapi.springboot.flow.user.IFlowOperator; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class FlowTrySubmitService { + + private final IFlowOperator currentOperator; + private final IBindData bindData; + private final Opinion opinion; + private final FlowServiceRepositoryHolder flowServiceRepositoryHolder; + + public FlowTrySubmitService(IFlowOperator currentOperator, + IBindData bindData, + Opinion opinion, + FlowServiceRepositoryHolder flowServiceRepositoryHolder) { + this.currentOperator = currentOperator; + this.bindData = bindData; + this.opinion = opinion; + this.flowServiceRepositoryHolder = flowServiceRepositoryHolder; + } + + + public FlowSubmitResult trySubmitFlow(long recordId) { + FlowSubmitService flowSubmitService = new FlowSubmitService(recordId, currentOperator, bindData, opinion, flowServiceRepositoryHolder); + return flowSubmitService.trySubmitFlow(); + } + + + public FlowSubmitResult trySubmitFlow(String workCode) { + FlowStartService flowStartService = new FlowStartService(workCode, currentOperator, bindData, opinion.getAdvice(), flowServiceRepositoryHolder); + FlowRecord flowRecord = flowStartService.tryStartFlow(); + + FlowSubmitService flowSubmitService = new FlowSubmitService(flowRecord, flowStartService.getFlowWork(), currentOperator, bindData, opinion, flowServiceRepositoryHolder); + return flowSubmitService.trySubmitFlow(); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowUrgeService.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowUrgeService.java new file mode 100644 index 00000000..ba2acda2 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/impl/FlowUrgeService.java @@ -0,0 +1,53 @@ +package com.codingapi.springboot.flow.service.impl; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.event.FlowApprovalEvent; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.FlowProcessRepository; +import com.codingapi.springboot.flow.repository.FlowRecordRepository; +import com.codingapi.springboot.flow.repository.FlowWorkRepository; +import com.codingapi.springboot.flow.service.FlowRecordVerifyService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.framework.event.EventPusher; +import lombok.AllArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Transactional +@AllArgsConstructor +public class FlowUrgeService { + + + private final FlowWorkRepository flowWorkRepository; + private final FlowRecordRepository flowRecordRepository; + private final FlowProcessRepository flowProcessRepository; + + /** + * 催办流程 + * + * @param recordId 流程记录id + * @param currentOperator 当前操作者 + */ + public void urge(long recordId, IFlowOperator currentOperator) { + FlowRecordVerifyService flowRecordVerifyService = new FlowRecordVerifyService( + flowWorkRepository, + flowRecordRepository, + flowProcessRepository, + recordId, currentOperator); + flowRecordVerifyService.loadFlowWork(); + flowRecordVerifyService.verifyFlowRecordIsDone(); + + FlowRecord flowRecord = flowRecordVerifyService.getFlowRecord(); + FlowWork flowWork = flowRecordVerifyService.getFlowWork(); + + List todoRecords = flowRecordRepository.findTodoFlowRecordByProcessId(flowRecord.getProcessId()); + + // 推送催办消息 + for (FlowRecord record : todoRecords) { + IFlowOperator pushOperator = record.getCurrentOperator(); + EventPusher.push(new FlowApprovalEvent(FlowApprovalEvent.STATE_URGE, record, pushOperator, flowWork, null), true); + } + + } +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/trigger/OutTrigger.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/trigger/OutTrigger.java new file mode 100644 index 00000000..b7281495 --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/trigger/OutTrigger.java @@ -0,0 +1,44 @@ +package com.codingapi.springboot.flow.trigger; + +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.script.GroovyShellContext; +import lombok.Getter; +import org.springframework.util.StringUtils; + +/** + * 出口触发器 + */ +public class OutTrigger { + + @Getter + private final String script; + + private final GroovyShellContext.ShellScript runtime; + + public OutTrigger(String script) { + if (!StringUtils.hasLength(script)) { + throw new IllegalArgumentException("script is empty"); + } + this.script = script; + this.runtime = GroovyShellContext.getInstance().parse(script); + } + + /** + * 默认出口触发器 + */ + public static OutTrigger defaultOutTrigger() { + return new OutTrigger("def run(content) {return true;}"); + } + + + /** + * 触发 + * + * @param flowSession 流程内容 + * @return true 进入下一节点,false 则返回上一节点 + */ + public boolean trigger(FlowSession flowSession) { + return (Boolean) runtime.invokeMethod("run", flowSession); + } + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/user/IFlowOperator.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/user/IFlowOperator.java new file mode 100644 index 00000000..69dfa44d --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/user/IFlowOperator.java @@ -0,0 +1,36 @@ +package com.codingapi.springboot.flow.user; + +/** + * 流程参与用户 + */ +public interface IFlowOperator { + + /** + * 获取用户ID + * + * @return ID + */ + long getUserId(); + + + /** + * 获取用户名称 + * @return 名称 + */ + String getName(); + + + /** + * 是否流程管理员 + * 流程管理员可以强制干预流程 + */ + boolean isFlowManager(); + + + /** + * 委托操作者 + * 当委托操作者不为空时,当前操作者将由委托操作者执行 + */ + IFlowOperator entrustOperator(); + +} diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/utils/Sha256Utils.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/utils/Sha256Utils.java new file mode 100644 index 00000000..c7a37b0b --- /dev/null +++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/utils/Sha256Utils.java @@ -0,0 +1,25 @@ +package com.codingapi.springboot.flow.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Sha256Utils { + + + public static String generateSHA256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/springboot-starter-flow/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-flow/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..3a985208 --- /dev/null +++ b/springboot-starter-flow/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.codingapi.springboot.flow.FlowConfiguration diff --git a/springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGeneratorApplication.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/FlowTestApplication.java similarity index 57% rename from springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGeneratorApplication.java rename to springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/FlowTestApplication.java index d7979615..8a5c85d0 100644 --- a/springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGeneratorApplication.java +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/FlowTestApplication.java @@ -1,12 +1,12 @@ -package com.codingapi.springboot.generator; +package com.codingapi.springboot.flow; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class IdGeneratorApplication { +public class FlowTestApplication { public static void main(String[] args) { - SpringApplication.run(IdGeneratorApplication.class, args); + SpringApplication.run(FlowTestApplication.class, args); } } diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java new file mode 100644 index 00000000..c7b3a7ca --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java @@ -0,0 +1,28 @@ +package com.codingapi.springboot.flow.flow; + +import com.codingapi.springboot.flow.bind.IBindData; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class Leave implements IBindData { + + private long id; + private String title; + private int days; + + public Leave(String title) { + this(title,0); + } + + public Leave(String title, int days) { + this.title = title; + this.days = days; + } + +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java new file mode 100644 index 00000000..c6612bc4 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave2.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.flow.flow; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Leave2 { + + private long id; + private String title; + private int days; + + public Leave2(String title) { + this(title,0); + } + + public Leave2(String title, int days) { + this.title = title; + this.days = days; + } + +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBackupRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBackupRepositoryImpl.java new file mode 100644 index 00000000..0efbb8cd --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBackupRepositoryImpl.java @@ -0,0 +1,29 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowBackup; + +import java.util.ArrayList; +import java.util.List; + +public class FlowBackupRepositoryImpl implements FlowBackupRepository{ + + private final List cache = new ArrayList<>(); + + @Override + public FlowBackup backup(FlowWork flowWork) { + FlowBackup flowBackup = new FlowBackup(flowWork); + cache.add(flowBackup); + return flowBackup; + } + + @Override + public FlowBackup getFlowBackupByWorkIdAndVersion(long workId, long workVersion) { + return cache.stream().filter(flowBackup -> flowBackup.getWorkId() == workId && flowBackup.getWorkVersion() == workVersion).findFirst().orElse(null); + } + + @Override + public FlowBackup getFlowBackupById(long backupId) { + return cache.stream().filter(flowBackup -> flowBackup.getId() == backupId).findFirst().orElse(null); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBindDataRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBindDataRepositoryImpl.java new file mode 100644 index 00000000..2f3b8228 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBindDataRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import lombok.AllArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class FlowBindDataRepositoryImpl implements FlowBindDataRepository { + + private final List cache = new ArrayList<>(); + + @Override + public void save(BindDataSnapshot snapshot) { + if (snapshot.getId() == 0) { + cache.add(snapshot); + snapshot.setId(cache.size()); + } + } + + @Override + public void update(BindDataSnapshot snapshot) { + BindDataSnapshot old = getBindDataSnapshotById(snapshot.getId()); + if (old != null) { + old.setSnapshot(snapshot.getSnapshot()); + } + } + + @Override + public BindDataSnapshot getBindDataSnapshotById(long id) { + return cache.stream().filter(snapshot -> snapshot.getId() == id).findFirst().orElse(null); + } + + public List findAll(){ + return cache; + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowProcessRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowProcessRepositoryImpl.java new file mode 100644 index 00000000..b128f599 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowProcessRepositoryImpl.java @@ -0,0 +1,42 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.record.FlowBackup; +import com.codingapi.springboot.flow.record.FlowProcess; +import lombok.AllArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@AllArgsConstructor +public class FlowProcessRepositoryImpl implements FlowProcessRepository { + + private final List cache = new ArrayList<>(); + private final FlowBackupRepository flowBackupRepository; + private final UserRepository userRepository; + + + @Override + public void save(FlowProcess flowProcess) { + List ids = cache.stream().map(FlowProcess::getProcessId).collect(Collectors.toList()); + if (!ids.contains(flowProcess.getProcessId())) { + cache.add(flowProcess); + } + } + + @Override + public FlowWork getFlowWorkByProcessId(String processId) { + FlowProcess process = cache.stream().filter(flowProcess -> flowProcess.getProcessId().equals(processId)).findFirst().orElse(null); + if (process == null) { + return null; + } + FlowBackup flowBackup = flowBackupRepository.getFlowBackupById(process.getBackupId()); + return flowBackup.resume(userRepository); + } + + @Override + public void deleteByProcessId(String processId) { + cache.removeIf(flowProcess -> flowProcess.getProcessId().equals(processId)); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java new file mode 100644 index 00000000..603d5e42 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java @@ -0,0 +1,180 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.query.FlowRecordQuery; +import com.codingapi.springboot.flow.record.FlowRecord; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class FlowRecordRepositoryImpl implements FlowRecordRepository, FlowRecordQuery { + + private final List cache = new ArrayList<>(); + + @Override + public void save(List records) { + for (FlowRecord record : records) { + if (record.getId() == 0) { + cache.add(record); + record.setId(cache.size()); + } + } + } + + @Override + public FlowRecord getFlowRecordById(long id) { + return cache.stream().filter(record -> record.getId() == id).findFirst().orElse(null); + } + + + @Override + public void update(FlowRecord flowRecord) { + if (flowRecord.getId() == 0) { + cache.add(flowRecord); + flowRecord.setId(cache.size()); + } + } + + @Override + public List findFlowRecordByPreId(long preId) { + return cache.stream().filter(record -> record.getPreId() == preId).collect(Collectors.toList()); + } + + @Override + public List findMergeFlowRecordById(String workCode, String nodeCode, long operatorId) { + return cache.stream() + .filter(record -> record.isTodo() && record.getCurrentOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + && record.getNodeCode().equals(nodeCode) + && record.isMergeable() + ) + .collect(Collectors.toList()); + } + + @Override + public List findFlowRecordByProcessId(String processId) { + return cache.stream().filter(record -> record.getProcessId().equals(processId)) + .sorted((o1, o2) -> (int) (o1.getCreateTime() - o2.getCreateTime())) + .collect(Collectors.toList()); + } + + @Override + public List findTodoFlowRecordByProcessId(String processId) { + return cache.stream().filter(record -> record.isTodo() && record.getProcessId().equals(processId)).collect(Collectors.toList()); + } + + public Page findAll(PageRequest pageRequest) { + return new PageImpl<>(cache); + } + + @Override + public Page findDoneByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isDone() && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findUnReadByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isUnRead() && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findUnReadByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isUnRead() && record.getWorkCode().equals(workCode) && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findDoneByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isDone() + && record.getCurrentOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + ).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findInitiatedByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isInitiated() && record.getCreateOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + + @Override + public Page findInitiatedByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter( + record -> record.isInitiated() + && record.getCreateOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + ).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + + @Override + public Page findTodoByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isTodo() && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findTodoByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isTodo() && record.getCurrentOperator().getUserId() == operatorId && record.getWorkCode().equals(workCode)).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public Page findTimeoutTodoByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isTimeout() && record.isTodo() && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + + @Override + public Page findTimeoutTodoByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter( + record -> record.isTimeout() + && record.isTodo() && record.getCurrentOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + ).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + + @Override + public Page findPostponedTodoByOperatorId(long operatorId, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isPostponed() && record.isTodo() && record.getCurrentOperator().getUserId() == operatorId).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + + @Override + public Page findPostponedTodoByOperatorId(long operatorId, String workCode, PageRequest pageRequest) { + List flowRecords = cache.stream().filter(record -> record.isPostponed() + && record.isTodo() && record.getCurrentOperator().getUserId() == operatorId + && record.getWorkCode().equals(workCode) + ).collect(Collectors.toList()); + return new PageImpl<>(flowRecords); + } + + @Override + public void finishFlowRecordByProcessId(String processId) { + cache.stream() + .filter(record -> record.getProcessId().equals(processId)) + .forEach(FlowRecord::finish); + } + + @Override + public void delete(List childrenRecords) { + cache.removeAll(childrenRecords); + } + + @Override + public void deleteByProcessId(String processId) { + cache.removeIf(record -> record.getProcessId().equals(processId)); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowWorkRepositoryImpl.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowWorkRepositoryImpl.java new file mode 100644 index 00000000..6e93dbea --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowWorkRepositoryImpl.java @@ -0,0 +1,34 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.domain.FlowWork; + +import java.util.ArrayList; +import java.util.List; + +public class FlowWorkRepositoryImpl implements FlowWorkRepository{ + + private final List cache = new ArrayList<>(); + + @Override + public FlowWork getFlowWorkById(long id) { + return cache.stream().filter(flowWork -> flowWork.getId() == id).findFirst().orElse(null); + } + + @Override + public FlowWork getFlowWorkByCode(String code) { + return cache.stream().filter(flowWork -> flowWork.getCode().equals(code)).findFirst().orElse(null); + } + + @Override + public void save(FlowWork flowWork) { + if(flowWork.getId()==0){ + cache.add(flowWork); + flowWork.setId(cache.size()); + } + } + + @Override + public void delete(long id) { + cache.removeIf(flowWork -> flowWork.getId() == id); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java new file mode 100644 index 00000000..1342286b --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.flow.Leave2; + +import java.util.ArrayList; +import java.util.List; + +public class LeaveRepository { + + private final List cache = new ArrayList<>(); + + public void save(Leave leave) { + if (leave.getId() == 0) { + cache.add(leave); + leave.setId(cache.size()); + } + } + public void save(Leave2 leave2) { + Leave leave = new Leave(leave2.getId(), leave2.getTitle(), leave2.getDays()); + this.save(leave); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/UserRepository.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/UserRepository.java new file mode 100644 index 00000000..315bc040 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/UserRepository.java @@ -0,0 +1,39 @@ +package com.codingapi.springboot.flow.repository; + +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.flow.user.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class UserRepository implements FlowOperatorRepository { + + private final List cache = new ArrayList<>(); + + public void save(User user) { + if (user.getId() == 0) { + cache.add(user); + user.setId(cache.size()); + } + } + + public User getById(long id) { + for (User user : cache) { + if (user.getId() == id) { + return user; + } + } + return null; + } + + @Override + public IFlowOperator getFlowOperatorById(long createOperatorId) { + return getById(createOperatorId); + } + + @Override + public List findByIds(List ids) { + return cache.stream().filter(user -> ids.contains(user.getId())).collect(Collectors.toList()); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/script/GroovyShellContextTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/script/GroovyShellContextTest.java new file mode 100644 index 00000000..80f655fd --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/script/GroovyShellContextTest.java @@ -0,0 +1,30 @@ +package com.codingapi.springboot.flow.script; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GroovyShellContextTest { + + @Test + void getInstance() { + long t1 = System.currentTimeMillis(); + int count = 12000; + GroovyShellContext.ShellScript[] scripts = new GroovyShellContext.ShellScript[count]; + for (int i = 0; i < count; i++) { + scripts[i] = GroovyShellContext.getInstance().parse("def run(content){ return '" + i + "';}"); + } + + long t2 = System.currentTimeMillis(); + System.out.println("t2 time :" + (t2 - t1)); + System.out.println("size:" + GroovyShellContext.getInstance().size()); + + for (int i = 0; i < count; i++) { + assertEquals(scripts[i].invokeMethod("run", i), String.valueOf(i)); + } + + long t3 = System.currentTimeMillis(); + System.out.println("t3 time :" + (t3 - t2)); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/BuildTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/BuildTest.java new file mode 100644 index 00000000..e9c739a9 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/BuildTest.java @@ -0,0 +1,52 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.repository.UserRepository; +import com.codingapi.springboot.flow.serializable.FlowWorkSerializable; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BuildTest { + + private final UserRepository userRepository = new UserRepository(); + + + @Test + void build() { + User user = new User("张三"); + userRepository.save(user); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("抄送节点", "circulate", "default", ApprovalType.CIRCULATE, OperatorMatcher.anyOperatorMatcher()) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("抄送节点", "manager", "circulate") + .relation("结束节点", "circulate", "over") + .build(); + assertEquals("请假流程", flowWork.getTitle()); + assertEquals(5, flowWork.getNodes().size()); + assertEquals(4, flowWork.getRelations().size()); + + + byte[] bytes = flowWork.toSerializable().toSerializable(); + FlowWorkSerializable flowWorkSerializable = FlowWorkSerializable.fromSerializable(bytes); + assertEquals("请假流程", flowWorkSerializable.getTitle()); + + FlowWork serializableWork = flowWorkSerializable.toFlowWork(userRepository); + assertEquals("请假流程", serializableWork.getTitle()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/CirculateTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/CirculateTest.java new file mode 100644 index 00000000..1a1c72cc --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/CirculateTest.java @@ -0,0 +1,143 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.pojo.FlowStepResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CirculateTest { + + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + + @Test + void circulate(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("抄送节点1", "circulate1", "default", ApprovalType.CIRCULATE, OperatorMatcher.specifyOperatorMatcher(lorne.getUserId())) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("抄送节点2", "circulate2", "default", ApprovalType.CIRCULATE, OperatorMatcher.specifyOperatorMatcher(user.getUserId())) + .node("抄送节点3", "circulate3", "default", ApprovalType.CIRCULATE, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + + .relations() + .relation("部门领导审批", "start", "dept") + .relation("抄送节点1", "dept", "circulate1") + .relation("总经理审批", "circulate1", "manager") + .relation("抄送节点1", "manager", "circulate2") + .relation("抄送节点2", "circulate2", "circulate3") + .relation("结束节点", "circulate3", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + + FlowStepResult result = flowService.getFlowStep(workCode, leave, user); + result.print(); + + // 查看我的待办 + List userTodos = flowRecordRepository.findUnReadByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + flowService.save(userTodo.getId(), user, leave,"暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(6, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(6, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + + // 查看lorne的未读 + List lorneTodos = flowRecordRepository.findUnReadByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, lorneTodos.size()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java new file mode 100644 index 00000000..d04a3124 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java @@ -0,0 +1,212 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.error.ErrTrigger; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ErrorTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + + /** + * 异常节点触发器 + * 异常时配置其他人来审批 + */ + @Test + void errorMatcherOperatorTest(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createOperatorErrTrigger("+dept.getId()+")}"), true,false) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + flowService.save(userTodo.getId(), user, leave,"暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", ((Leave)flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + } + + + + /** + * 异常节点触发器 + * 异常时配置其节点来审批 + */ + @Test + void errorMatcherNodeTest(){ + + PageRequest pageRequest = PageRequest.of(0,1000); + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, new OperatorMatcher("def run(content){return []}"), new ErrTrigger("def run(content){return content.createNodeErrTrigger('manager')}"), true,false) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + flowService.save(userTodo.getId(), user, leave,"暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", ((Leave)flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(2, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(2, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(3, snapshots.size()); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java new file mode 100644 index 00000000..a14c7e98 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowMapTest.java @@ -0,0 +1,127 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.bind.FlowMapBindData; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave2; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FlowMapTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository, userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository, flowProcessRepository, flowBackupRepository); + + /** + * map数据绑定对象测试 + */ + @Test + void mapFlowTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave2 leave = new Leave2("我要出去看看"); + FlowMapBindData bindData = FlowMapBindData.fromObject(leave); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, bindData, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + bindData = FlowMapBindData.fromObject(leave); + flowService.save(userTodo.getId(), user, bindData, "暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", (flowDetail.getBindData().toJavaObject(Leave2.class)).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, bindData, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, bindData, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, bindData, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java new file mode 100644 index 00000000..bf6024b9 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java @@ -0,0 +1,1109 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.pojo.FlowStepResult; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class FlowTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 委托测试测试 + */ + @Test + void entrustTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备", lorne); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + FlowStepResult result = flowService.getFlowStep(workCode, leave, user); + result.print(); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + assertEquals(0, userTodo.getTimeoutTime()); + // 保存流程 + leave.setTitle("我要出去看看~~"); + flowService.save(userTodo.getId(), user, leave,"暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看刘备经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + List lorneTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, lorneTodos.size()); + + // 提交委托lorne部门经理的审批 + FlowRecord lorneTodo = lorneTodos.get(0); + flowService.submitFlow(lorneTodo.getId(), lorne, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } + + + + + /** + * 自己审批时直接通过 + */ + @Test + void sameUserFlow() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .skipIfSameApprover(true) + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + assertEquals(0, userTodo.getTimeoutTime()); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看刘备经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交委托dept部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } + + + /** + * 同意再拒绝 + */ + @Test + void passAndRejectTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("总经理审批", "manager", "default", ApprovalType.SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.creatorOperatorMatcher()) + .relations() + .relation("开始节点", "start", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(1, records.size()); + + + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(2, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(3, snapshots.size()); + + } + + /** + * 全部通过测试 + */ + @Test + void passTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + // 保存流程 + leave.setTitle("我要出去看看~~"); + flowService.save(userTodo.getId(), user, leave,"暂存"); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId(), user); + assertEquals("我要出去看看~~", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } + + + /** + * 节点禁止保存通过测试 + */ + @Test + void saveDisableTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId()), false,false) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + leave.setTitle("我要出去看看~~"); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + assertEquals("我要出去看看", ((Leave) flowService.detail(deptTodo.getId()).getBindData()).getTitle()); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(3, snapshots.size()); + + } + + /** + * 干预流程 + */ + @Test + void interfereTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User admin = new User("lorne", true); + userRepository.save(admin); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.creatorOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.interfere(userTodo.getId(), admin, leave, Opinion.pass("同意")); + assertTrue(userTodo.isInterfere()); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.interfere(deptTodo.getId(), admin, leave, Opinion.pass("同意")); + assertTrue(deptTodo.isInterfere()); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.interfere(bossTodo.getId(), admin, leave, Opinion.pass("同意")); + assertTrue(bossTodo.isInterfere()); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + } + + + /** + * 转办流程 + */ + @Test + void transferTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + + // 转交给lorne处理 + FlowRecord deptTodo = deptTodos.get(0); + flowService.transfer(deptTodo.getId(), dept, lorne, leave, "转办给lorne"); + + assertTrue(deptTodo.isTransfer()); + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + List lorneTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, lorneTodos.size()); + + FlowRecord lorneTodo = lorneTodos.get(0); + + flowService.submitFlow(lorneTodo.getId(), lorne, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(4, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(4, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(5, snapshots.size()); + } + + + /** + * 催办与延期测试 + */ + @Test + void postponedAndUrgeTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.creatorOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId()); + assertEquals("我要出去看看", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isUnRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + + FlowRecord deptTodo = deptTodos.get(0); + + + // 延期10000毫米 + flowService.postponed(deptTodo.getId(), dept, 10000); + + long latestTimeOutTime = deptTodo.getTimeoutTime(); + long currentTime = System.currentTimeMillis(); + + assertTrue(latestTimeOutTime - currentTime >= 10000); + + // 再延期将会出现异常 + assertThrows(Exception.class, () -> flowService.postponed(deptTodo.getId(), dept, 10000)); + + // 催办 + flowService.urge(userTodo.getId(), user); + + // 待办下催办出现异常 + assertThrows(Exception.class, () -> flowService.postponed(deptTodo.getId(), dept, 10000)); + + } + + /** + * 一直退回的测试 + */ + @Test + void rejectAllTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .skipIfSameApprover(true) + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(user.getUserId())) + .node("办公室领导审批", "office", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("办公室领导审批", "dept", "office") + .relation("总经理审批", "office", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId()); + assertEquals("我要出去看看", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isUnRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.reject("不同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + + System.out.println(userTodo.getNodeCode()); + + assertEquals("start", userTodo.getNodeCode()); + } + + + /** + * 部门拒绝再提交测试 + */ + @Test + void rejectTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId()); + assertEquals("我要出去看看", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isUnRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(5, records.size()); + + + } + + + /** + * 撤销流程测试 + */ + @Test + void recallTest1() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId(),lorne.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + FlowSubmitResult flowSubmitResult = flowService.trySubmitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + assertEquals(flowSubmitResult.getOperators().size(), 2); + assertTrue(flowSubmitResult.getOperators().stream().map(IFlowOperator::getUserId).collect(Collectors.toList()).contains(dept.getUserId())); + assertTrue(flowSubmitResult.getOperators().stream().map(IFlowOperator::getUserId).collect(Collectors.toList()).contains(lorne.getUserId())); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意").specify(dept.getUserId())); + + + // 撤销流程 + flowService.recall(userTodo.getId(), user); + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意").specify(dept.getUserId())); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意").specify(dept.getUserId())); + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(5, records.size()); + + + } + + + + /** + * 删除流程测试 + */ + @Test + void removeTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId(),lorne.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + FlowSubmitResult flowSubmitResult = flowService.trySubmitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + assertEquals(flowSubmitResult.getOperators().size(), 2); + assertTrue(flowSubmitResult.getOperators().stream().map(IFlowOperator::getUserId).collect(Collectors.toList()).contains(dept.getUserId())); + assertTrue(flowSubmitResult.getOperators().stream().map(IFlowOperator::getUserId).collect(Collectors.toList()).contains(lorne.getUserId())); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意").specify(dept.getUserId())); + + + // 撤销流程 + flowService.recall(userTodo.getId(), user); + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(1, records.size()); + + flowService.remove(userTodo.getId(), user); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(0, records.size()); + + } + + + /** + * 撤销流程测试 + */ + @Test + void recallTest2() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 撤销流程 + FlowRecord userTodo = userTodos.get(0); + flowService.recall(userTodo.getId(), user); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(0, records.size()); + + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest2.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest2.java new file mode 100644 index 00000000..7ea093dc --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest2.java @@ -0,0 +1,298 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class FlowTest2 { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 转办测试 + */ + @Test + void flowTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + User lorne = new User("lorne"); + userRepository.save(lorne); + + User boss = new User("boss"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(lorne) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("老板审批", "boss", "default", ApprovalType.SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("老板审批", "start", "boss") + .relation("结束节点", "boss", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我想要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, lorne, leave, "发起流程"); + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), lorne, leave, Opinion.pass("自己提交")); + + // 部门领导审批 + List deptTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + FlowRecord deptTodo = deptTodos.get(0); + assertNull(deptTodo.getOpinion()); + flowService.transfer(deptTodo.getId(), lorne,boss, leave, "转交给领导审批通过"); + + // 查看boss的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.reject("领导审批不通过")); + + userTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + assertEquals(FlowNode.CODE_START, userTodo.getNodeCode()); + + flowService.submitFlow(userTodo.getId(), lorne, leave, Opinion.pass("自己再次提交")); + + deptTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), lorne, leave, Opinion.pass("转交给领导审批通过")); + + bossTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(5, records.size()); + + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + } + + + /** + * 流程等待测试 + */ + @Test + void flowWaitingTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + User lorne = new User("lorne"); + userRepository.save(lorne); + + User boss = new User("boss"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(lorne) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("老板审批", "boss", "default", ApprovalType.SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("老板审批", "boss1", "default", ApprovalType.SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("老板审批", "start", "boss") + .relation("老板审批1", "boss", "boss1") + .relation("结束节点", "boss1", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我想要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, lorne, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + String processId = userTodos.get(0).getProcessId(); + + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), lorne, leave, Opinion.pass("我提交了")); + + // 查看boss的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.waiting("我等待提交")); + + userTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + // 通知流程 + flowService.notifyFlow(processId,boss); + + // 查看boss的待办 + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("领导审批通过")); + + bossTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + } + + + /** + * 部门拒绝再提交测试 + */ + @Test + void rejectTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + // 查看流程详情 + FlowDetail flowDetail = flowService.detail(userTodo.getId()); + assertEquals("我要出去看看", ((Leave) flowDetail.getBindData()).getTitle()); + assertTrue(flowDetail.getFlowRecord().isUnRead()); + + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看老板审批 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 老板审批不通过 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.reject("不同意")); + + // 部门经理查看到流程 + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + // 查看用户的待办 + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 用户再次提交 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 部门经理查看到流程 + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看老板审批 + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 老板审批通过 + bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(7, records.size()); + + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest3.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest3.java new file mode 100644 index 00000000..427eef19 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest3.java @@ -0,0 +1,143 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FlowTest3 { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository, userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository, flowProcessRepository, flowBackupRepository); + + + /** + * 一直退回的测试 + */ + @Test + void rejectAllTest() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .skipIfSameApprover(true) + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门负责人审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(user.getUserId(), boss.getUserId())) + .node("分管领导审批", "office", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门负责人审批", "start", "dept") + .relation("分管领导审批", "dept", "office") + .relation("总经理审批", "office", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.reject("不同意")); + + List records = flowRecordRepository.findAll(pageRequest).getContent(); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + + System.out.println(userTodo.getNodeCode()); + + assertEquals("start", userTodo.getNodeCode()); + + // 提交流程 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("不同意")); + + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.reject("不同意")); + + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + + System.out.println(userTodo.getNodeCode()); + + assertEquals("start", userTodo.getNodeCode()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(11, records.size()); + + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/MultiRelationFlowTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/MultiRelationFlowTest.java new file mode 100644 index 00000000..dea70b23 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/MultiRelationFlowTest.java @@ -0,0 +1,304 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MultiRelationFlowTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 多条件流程测试 + * (直接走结束,没有流转老板测试) + */ + @Test + void relationTest1(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "over",new OutTrigger("def run(content){content.getBindData().getDays()<=5}"),1,false) + .relation("总经理审批", "dept", "manager",new OutTrigger("def run(content){content.getBindData().getDays()>5}"),2,false) + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看",5); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(2, records.size()); + + // 最终用户确认 + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(2, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(3, snapshots.size()); + } + + + + + /** + * 多条件流程测试 + * (流转老板测试) + */ + @Test + void relationTest2(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "over",new OutTrigger("def run(content){content.getBindData().getDays()<=5}"),1,false) + .relation("总经理审批", "dept", "manager",new OutTrigger("def run(content){content.getBindData().getDays()>5}"),2,false) + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看",6); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + + // 查看老板的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交老板的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + // 最终用户确认 + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + } + + + + /** + * 多条件流程测试撤回 + */ + @Test + void relationTest3(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "over",new OutTrigger("def run(content){content.getBindData().getDays()<=5}"),1,false) + .relation("总经理审批", "dept", "manager",new OutTrigger("def run(content){content.getBindData().getDays()>5}"),2,false) + .relation("结束节点", "manager", "start",new OutTrigger("def run(content){return true}"),1,true) + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看",6); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + + // 查看老板的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交老板的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.reject("不同意,最多让你请假3天")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(4, records.size()); + + // 用户修改确认 + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 用户调整为3天 + leave.setDays(3); + // 提交流程 + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(5, records.size()); + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 用户修改确认 + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(6, snapshots.size()); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/QueryTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/QueryTest.java new file mode 100644 index 00000000..b4da2889 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/QueryTest.java @@ -0,0 +1,527 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowDetail; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class QueryTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 查询用户的待办 + */ + @Test + void queryUserToDo(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + } + + + /** + * 查询用户的超时待办 + */ + @Test + void queryUserTimeoutTodo(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher(),100,true) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 查看我的超时待办 + List userTimeOutTodos = flowRecordRepository.findTimeoutTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTimeOutTodos.size()); + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) {} + + userTimeOutTodos = flowRecordRepository.findTimeoutTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTimeOutTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent();; + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + userTodos = flowRecordRepository.findTimeoutTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + deptTodos = flowRecordRepository.findTimeoutTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + bossTodos = flowRecordRepository.findTimeoutTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + } + + + + /** + * 查询用户的延期待办 + */ + @Test + void queryUserPostponedTodo(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher(),100,true) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 延期待办 + FlowRecord userTodo = userTodos.get(0); + flowService.postponed(userTodo.getId(), user,100); + + // 查看我的延期待办 + List userPostponedTodos = flowRecordRepository.findPostponedTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userPostponedTodos.size()); + + // 查看我的超时待办 + List userTimeOutTodos = flowRecordRepository.findTimeoutTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTimeOutTodos.size()); + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) {} + + userTimeOutTodos = flowRecordRepository.findTimeoutTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTimeOutTodos.size()); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + userTodos = flowRecordRepository.findPostponedTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + deptTodos = flowRecordRepository.findPostponedTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + bossTodos = flowRecordRepository.findPostponedTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + } + + + /** + * 查询用户的已办 + */ + @Test + void queryUserDone(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + FlowDetail flowDetail = flowService.detail(records.get(0).getId(), user); + assertEquals(3, flowDetail.getHistoryRecords().size()); + assertEquals(3, flowDetail.getOpinions().size()); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + + List userDones = flowRecordRepository.findDoneByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userDones.size()); + + List deptDones = flowRecordRepository.findDoneByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptDones.size()); + + List bossDones = flowRecordRepository.findDoneByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossDones.size()); + + assertTrue(bossDones.stream().allMatch(FlowRecord::isRead)); + } + + + /** + * 查询用户发起的流程 + */ + @Test + void queryUserInitiated(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + + + List userInitiates = flowRecordRepository.findInitiatedByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userInitiates.size()); + + List deptInitiates = flowRecordRepository.findInitiatedByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptInitiates.size()); + + List bossInitiates = flowRecordRepository.findInitiatedByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossInitiates.size()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java new file mode 100644 index 00000000..a319393a --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java @@ -0,0 +1,44 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowNode; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class ScriptBuildTest { + + + @Test + void copy() { + User user = new User("张三"); + String script = "{\"nodes\":[{\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"type\":\"start-node\",\"x\":593,\"y\":96,\"properties\":{\"name\":\"开始节点\",\"code\":\"start\",\"type\":\"START\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"type\":\"node-node\",\"x\":620,\"y\":239,\"properties\":{\"name\":\"流程节点\",\"code\":\"flow\",\"type\":\"APPROVAL\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '8899-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"SIGN\",\"timeout\":10,\"id\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"custom\",\"errTriggerType\":\"custom\"}},{\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"type\":\"over-node\",\"x\":828,\"y\":582,\"properties\":{\"name\":\"结束节点\",\"code\":\"over\",\"type\":\"OVER\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCurrentOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"UN_SIGN\",\"timeout\":0,\"id\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"width\":200,\"height\":45,\"operatorMatcherType\":\"any\",\"titleGeneratorType\":\"default\",\"errTriggerType\":\"custom\"}},{\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"type\":\"circulate-node\",\"x\":839,\"y\":409,\"properties\":{\"name\":\"抄送节点\",\"code\":\"circulate\",\"type\":\"CIRCULATE\",\"view\":\"default\",\"operatorMatcher\":\"def run(content) {return [content.getCreateOperator().getUserId()];}\",\"editable\":true,\"mergeable\":true,\"titleGenerator\":\"def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}\",\"errTrigger\":\"\",\"approvalType\":\"CIRCULATE\",\"timeout\":0,\"id\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"width\":200,\"height\":45}}],\"edges\":[{\"id\":\"b68837fb-dca8-41d2-908c-dc079a7f61de\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"b82a84e7-2c1d-4e15-a3c5-6f7f6e263acd\",\"targetNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"startPoint\":{\"x\":593,\"y\":118.5},\"endPoint\":{\"x\":620,\"y\":216.5},\"pointsList\":[{\"x\":593,\"y\":118.5},{\"x\":593,\"y\":218.5},{\"x\":620,\"y\":116.5},{\"x\":620,\"y\":216.5}]},{\"id\":\"73e04b95-50f6-44cc-a960-d3007d27fd48\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":2,\"back\":false},\"sourceNodeId\":\"3c2c420a-003b-4f51-9489-3cdcda0bbe35\",\"targetNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"startPoint\":{\"x\":720,\"y\":239},\"endPoint\":{\"x\":739,\"y\":409},\"pointsList\":[{\"x\":720,\"y\":239},{\"x\":820,\"y\":239},{\"x\":639,\"y\":409},{\"x\":739,\"y\":409}]},{\"id\":\"f6929c79-b168-4c3c-9f8f-9dc21fcaf29d\",\"type\":\"bezier\",\"properties\":{\"outTrigger\":\"def run(content) {return true;}\",\"order\":1,\"back\":false},\"sourceNodeId\":\"2ecdb8aa-00b2-42af-b3ed-c776d2431b38\",\"targetNodeId\":\"b527b4a5-f11f-4052-9848-2c0426da970c\",\"startPoint\":{\"x\":839,\"y\":431.5},\"endPoint\":{\"x\":828,\"y\":559.5},\"pointsList\":[{\"x\":839,\"y\":431.5},{\"x\":839,\"y\":531.5},{\"x\":828,\"y\":459.5},{\"x\":828,\"y\":559.5}]}]}"; + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .schema(script) + .build(); + assertEquals("请假流程", flowWork.getTitle()); + assertEquals(4, flowWork.getNodes().size()); + assertEquals(3, flowWork.getRelations().size()); + + + FlowNode startNode = flowWork.getStartNode(); + + FlowWork copyWork = flowWork.copy(); + assertNotEquals(copyWork.getCode(), flowWork.getCode()); + + assertEquals("请假流程", copyWork.getTitle()); + assertEquals(4, copyWork.getNodes().size()); + assertEquals(3, copyWork.getRelations().size()); + + copyWork.verify(); + + FlowNode startNode2 = copyWork.getNodeByCode("start"); + assertNotEquals(startNode.getId(), startNode2.getId()); + + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptTest.java new file mode 100644 index 00000000..7f1abf00 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptTest.java @@ -0,0 +1,72 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.content.FlowSession; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.generator.TitleGenerator; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.repository.UserRepository; +import com.codingapi.springboot.flow.trigger.OutTrigger; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ScriptTest { + + private final UserRepository userRepository = new UserRepository(); + + @Test + void test() { + User user = new User("张三"); + userRepository.save(user); + + OperatorMatcher matcher = OperatorMatcher.anyOperatorMatcher(); + + OperatorMatcher operatorMatcher = OperatorMatcher.anyOperatorMatcher(); + + TitleGenerator titleGenerator = TitleGenerator.defaultTitleGenerator(); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, operatorMatcher) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, operatorMatcher) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, operatorMatcher) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, operatorMatcher) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + OutTrigger outTrigger =OutTrigger.defaultOutTrigger(); + OperatorMatcher specifyOperatorMatcher = OperatorMatcher.specifyOperatorMatcher(1); + + long now = System.currentTimeMillis(); + Leave leave = new Leave("我要请假"); + + FlowSession flowSession = new FlowSession(null,flowWork, flowWork.getNodeByCode("start"), user, user, leave, Opinion.pass("同意"),new ArrayList<>()); + + List ids = matcher.matcher(flowSession); + assertTrue(ids.contains(user.getUserId())); + + String title = titleGenerator.generate(flowSession); + assertEquals("张三-请假流程-开始节点", title); + + boolean next = outTrigger.trigger(flowSession); + assertTrue(next); + + List userIds = specifyOperatorMatcher.matcher(flowSession); + assertTrue(userIds.contains(1L)); + + long time = System.currentTimeMillis() - now; + System.out.println("time:" + time); + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/SignTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/SignTest.java new file mode 100644 index 00000000..6c47cf15 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/SignTest.java @@ -0,0 +1,625 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.IFlowOperator; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SignTest { + + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 多人非会签测试 + */ + @Test + void unSignTest(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User caocao = new User("曹操"); + userRepository.save(caocao); + User lvBu = new User("吕布"); + userRepository.save(lvBu); + User zhaoYun = new User("赵云"); + userRepository.save(zhaoYun); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, + OperatorMatcher.specifyOperatorMatcher(dept.getUserId(),caocao.getUserId(),lvBu.getUserId(),zhaoYun.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(zhaoYun.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), zhaoYun, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent();; + assertEquals(6, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(6, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } + + + /** + * 多人非会签拒绝测试 + */ + @Test + void unSignRejectTest(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User caocao = new User("曹操"); + userRepository.save(caocao); + User lvBu = new User("吕布"); + userRepository.save(lvBu); + User zhaoYun = new User("赵云"); + userRepository.save(zhaoYun); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, + OperatorMatcher.specifyOperatorMatcher(dept.getUserId(),caocao.getUserId(),lvBu.getUserId(),zhaoYun.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意").specify(lvBu.getUserId())); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + // 查看部门刘备经理的待办 + List lvBuTodos = flowRecordRepository.findTodoByOperatorId(lvBu.getUserId(), pageRequest).getContent(); + assertEquals(1, lvBuTodos.size()); + + // 提交部门经理的审批 + FlowRecord lvBuTodo = lvBuTodos.get(0); + flowService.submitFlow(lvBuTodo.getId(), lvBu, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.reject("不同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent();; + assertEquals(4, records.size()); + + lvBuTodos = flowRecordRepository.findTodoByOperatorId(lvBu.getUserId(), pageRequest).getContent(); + assertEquals(1, lvBuTodos.size()); + + // 提交部门经理的审批 + lvBuTodo = lvBuTodos.get(0); + flowService.submitFlow(lvBuTodo.getId(), lvBu, leave, Opinion.pass("同意")); + + + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("行吧")); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(5, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(6, snapshots.size()); + + } + + + /** + * 多人会签测试 + */ + @Test + void signTest(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User caocao = new User("曹操"); + userRepository.save(caocao); + User lvBu = new User("吕布"); + userRepository.save(lvBu); + User zhaoYun = new User("赵云"); + userRepository.save(zhaoYun); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.SIGN, + OperatorMatcher.specifyOperatorMatcher(dept.getUserId(),caocao.getUserId(),lvBu.getUserId(),zhaoYun.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("用户同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("刘备同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + // 查看部门经理 吕布 的待办 + List lvbuTodos = flowRecordRepository.findTodoByOperatorId(lvBu.getUserId(), pageRequest).getContent(); + assertEquals(1, lvbuTodos.size()); + + // 提交部门经理 吕布 的审批 + FlowRecord lvbuTodo = lvbuTodos.get(0); + flowService.submitFlow(lvbuTodo.getId(), lvBu, leave, Opinion.pass("吕布同意")); + + + // 查看部门经理 赵云 的待办 + List zhaoYunTodos = flowRecordRepository.findTodoByOperatorId(zhaoYun.getUserId(), pageRequest).getContent(); + assertEquals(1, zhaoYunTodos.size()); + + // 提交部门经理 赵云 的审批 + FlowRecord zhaoYunTodo = zhaoYunTodos.get(0); + flowService.submitFlow(zhaoYunTodo.getId(), zhaoYun, leave, Opinion.pass("赵云同意")); + + + // 查看部门经理 曹操 的待办 + List caocaoTodos = flowRecordRepository.findTodoByOperatorId(caocao.getUserId(), pageRequest).getContent(); + assertEquals(1, caocaoTodos.size()); + + // 提交部门经理 曹操 的审批 + FlowRecord caocaoTodo = caocaoTodos.get(0); + flowService.submitFlow(caocaoTodo.getId(), caocao, leave, Opinion.pass("曹操同意")); + + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(6, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(6, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(7, snapshots.size()); + + } + + + + + /** + * 多人会签 有人拒绝测试 + */ + @Test + void signRejectTest(){ + + PageRequest pageRequest = PageRequest.of(0, 1000); + + User caocao = new User("曹操"); + userRepository.save(caocao); + User lvBu = new User("吕布"); + userRepository.save(lvBu); + User zhaoYun = new User("赵云"); + userRepository.save(zhaoYun); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.SIGN, + OperatorMatcher.specifyOperatorMatcher( + dept.getUserId(), + caocao.getUserId(), + lvBu.getUserId(), + zhaoYun.getUserId() + )) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("用户同意")); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.reject("刘备不同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + // 查看部门经理 吕布 的待办 + List lvbuTodos = flowRecordRepository.findTodoByOperatorId(lvBu.getUserId(), pageRequest).getContent(); + assertEquals(1, lvbuTodos.size()); + + // 提交部门经理 吕布 的审批 + FlowRecord lvbuTodo = lvbuTodos.get(0); + flowService.submitFlow(lvbuTodo.getId(), lvBu, leave, Opinion.pass("吕布同意")); + + + // 查看部门经理 赵云 的待办 + List zhaoYunTodos = flowRecordRepository.findTodoByOperatorId(zhaoYun.getUserId(), pageRequest).getContent(); + assertEquals(1, zhaoYunTodos.size()); + + // 提交部门经理 赵云 的审批 + FlowRecord zhaoYunTodo = zhaoYunTodos.get(0); + flowService.submitFlow(zhaoYunTodo.getId(), zhaoYun, leave, Opinion.pass("赵云同意")); + + + // 查看部门经理 曹操 的待办 + List caocaoTodos = flowRecordRepository.findTodoByOperatorId(caocao.getUserId(), pageRequest).getContent(); + assertEquals(1, caocaoTodos.size()); + + // 提交部门经理 曹操 的审批 + FlowRecord caocaoTodo = caocaoTodos.get(0); + flowService.submitFlow(caocaoTodo.getId(), caocao, leave, Opinion.pass("曹操同意")); + + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(0, bossTodos.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("用户同意")); + + + deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + deptTodo = deptTodos.get(0); + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("刘备同意")); + + + lvbuTodos = flowRecordRepository.findTodoByOperatorId(lvBu.getUserId(), pageRequest).getContent(); + assertEquals(1, lvbuTodos.size()); + + // 提交部门经理 吕布 的审批 + lvbuTodo = lvbuTodos.get(0); + flowService.submitFlow(lvbuTodo.getId(), lvBu, leave, Opinion.pass("吕布同意")); + + + zhaoYunTodos = flowRecordRepository.findTodoByOperatorId(zhaoYun.getUserId(), pageRequest).getContent(); + assertEquals(1, zhaoYunTodos.size()); + + // 提交部门经理 赵云 的审批 + zhaoYunTodo = zhaoYunTodos.get(0); + flowService.submitFlow(zhaoYunTodo.getId(), zhaoYun, leave, Opinion.pass("赵云同意")); + + caocaoTodos = flowRecordRepository.findTodoByOperatorId(caocao.getUserId(), pageRequest).getContent(); + assertEquals(1, caocaoTodos.size()); + + // 提交部门经理 曹操 的审批 + caocaoTodo = caocaoTodos.get(0); + flowService.submitFlow(caocaoTodo.getId(), caocao, leave, Opinion.pass("曹操同意")); + + + bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent();; + assertEquals(11, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(11, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(12, snapshots.size()); + + } + + + + /** + * 多人会签trySubmit测试 + */ + @Test + void signTrySubmitTest(){ + PageRequest pageRequest = PageRequest.of(0, 1000); + + User caocao = new User("曹操"); + userRepository.save(caocao); + User lvBu = new User("吕布"); + userRepository.save(lvBu); + User zhaoYun = new User("赵云"); + userRepository.save(zhaoYun); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备"); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + List signUsers = new ArrayList<>(); + signUsers.add(dept); + signUsers.add(caocao); + signUsers.add(lvBu); + signUsers.add(zhaoYun); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.SIGN, OperatorMatcher.specifyOperatorMatcher(signUsers.stream().map(User::getUserId).collect(Collectors.toList()))) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN,OperatorMatcher.specifyOperatorMatcher(boss.getUserId()) ) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + + // 验证会签的人员 + FlowSubmitResult submitResult = flowService.trySubmitFlow(userTodo.getId(), user, leave, Opinion.pass("用户同意")); + List operators = submitResult.getOperators(); + assertEquals(signUsers.size(), operators.size()); + + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("用户同意").specify(dept.getUserId())); + + // 查看部门经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(1, deptTodos.size()); + + // 提交部门经理的审批 + FlowRecord deptTodo = deptTodos.get(0); + + // 验证会签的人员 + submitResult = flowService.trySubmitFlow(deptTodo.getId(), dept, leave, Opinion.pass("用户同意")); + operators = submitResult.getOperators(); + assertEquals(1, operators.size()); + assertEquals(boss.getName(), operators.get(0).getName()); + + flowService.submitFlow(deptTodo.getId(), dept, leave, Opinion.pass("刘备同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/TrySubmitTest.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/TrySubmitTest.java new file mode 100644 index 00000000..594201d6 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/TrySubmitTest.java @@ -0,0 +1,134 @@ +package com.codingapi.springboot.flow.test; + +import com.codingapi.springboot.flow.bind.BindDataSnapshot; +import com.codingapi.springboot.flow.build.FlowWorkBuilder; +import com.codingapi.springboot.flow.domain.FlowWork; +import com.codingapi.springboot.flow.domain.Opinion; +import com.codingapi.springboot.flow.em.ApprovalType; +import com.codingapi.springboot.flow.flow.Leave; +import com.codingapi.springboot.flow.matcher.OperatorMatcher; +import com.codingapi.springboot.flow.pojo.FlowSubmitResult; +import com.codingapi.springboot.flow.record.FlowRecord; +import com.codingapi.springboot.flow.repository.*; +import com.codingapi.springboot.flow.service.FlowService; +import com.codingapi.springboot.flow.user.User; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TrySubmitTest { + + private final UserRepository userRepository = new UserRepository(); + private final FlowWorkRepository flowWorkRepository = new FlowWorkRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final FlowBindDataRepositoryImpl flowBindDataRepository = new FlowBindDataRepositoryImpl(); + private final LeaveRepository leaveRepository = new LeaveRepository(); + private final FlowBackupRepository flowBackupRepository = new FlowBackupRepositoryImpl(); + private final FlowProcessRepository flowProcessRepository = new FlowProcessRepositoryImpl(flowBackupRepository,userRepository); + private final FlowService flowService = new FlowService(flowWorkRepository, flowRecordRepository, flowBindDataRepository, userRepository,flowProcessRepository,flowBackupRepository); + + /** + * 预提交测试 + */ + @Test + void test() { + PageRequest pageRequest = PageRequest.of(0, 1000); + + User lorne = new User("lorne"); + userRepository.save(lorne); + + User user = new User("张飞"); + userRepository.save(user); + + User dept = new User("刘备", lorne); + userRepository.save(dept); + + User boss = new User("诸葛亮"); + userRepository.save(boss); + + FlowWork flowWork = FlowWorkBuilder.builder(user) + .title("请假流程") + .nodes() + .node("开始节点", "start", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .node("部门领导审批", "dept", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(dept.getUserId())) + .node("总经理审批", "manager", "default", ApprovalType.UN_SIGN, OperatorMatcher.specifyOperatorMatcher(boss.getUserId())) + .node("结束节点", "over", "default", ApprovalType.UN_SIGN, OperatorMatcher.anyOperatorMatcher()) + .relations() + .relation("部门领导审批", "start", "dept") + .relation("总经理审批", "dept", "manager") + .relation("结束节点", "manager", "over") + .build(); + + flowWorkRepository.save(flowWork); + + String workCode = flowWork.getCode(); + + Leave leave = new Leave("我要出去看看"); + leaveRepository.save(leave); + + FlowSubmitResult flowSubmitResult = flowService.trySubmitFlow(workCode, user, leave, Opinion.pass("发起流程")); + // 下级审批人有1个 + assertEquals(1, flowSubmitResult.getOperators().size()); + // 下级审批人是刘备 + assertEquals(dept.getUserId(), flowSubmitResult.getOperators().get(0).getUserId()); + + // 创建流程 + flowService.startFlow(workCode, user, leave, "发起流程"); + + // 查看我的待办 + List userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(1, userTodos.size()); + + + flowSubmitResult = flowService.trySubmitFlow(userTodos.get(0).getId(), user, leave, Opinion.pass("同意")); + // 下级审批人有1个 + assertEquals(1, flowSubmitResult.getOperators().size()); + // 下级审批人是刘备 + assertEquals(dept.getUserId(), flowSubmitResult.getOperators().get(0).getUserId()); + + + // 提交流程 + FlowRecord userTodo = userTodos.get(0); + flowService.submitFlow(userTodo.getId(), user, leave, Opinion.pass("同意")); + + // 查看刘备经理的待办 + List deptTodos = flowRecordRepository.findTodoByOperatorId(dept.getUserId(), pageRequest).getContent(); + assertEquals(0, deptTodos.size()); + + List lorneTodos = flowRecordRepository.findTodoByOperatorId(lorne.getUserId(), pageRequest).getContent(); + assertEquals(1, lorneTodos.size()); + + // 提交委托lorne部门经理的审批 + FlowRecord lorneTodo = lorneTodos.get(0); + flowService.submitFlow(lorneTodo.getId(), lorne, leave, Opinion.pass("同意")); + + // 查看总经理的待办 + List bossTodos = flowRecordRepository.findTodoByOperatorId(boss.getUserId(), pageRequest).getContent(); + assertEquals(1, bossTodos.size()); + + // 提交总经理的审批 + FlowRecord bossTodo = bossTodos.get(0); + flowService.submitFlow(bossTodo.getId(), boss, leave, Opinion.pass("同意")); + + // 查看所有流程 + List records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + + userTodos = flowRecordRepository.findTodoByOperatorId(user.getUserId(), pageRequest).getContent(); + assertEquals(0, userTodos.size()); + + records = flowRecordRepository.findAll(pageRequest).getContent(); + assertEquals(3, records.size()); + // 查看所有流程是否都已经结束 + assertTrue(records.stream().allMatch(FlowRecord::isFinish)); + + List snapshots = flowBindDataRepository.findAll(); + assertEquals(4, snapshots.size()); + + } + +} diff --git a/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/user/User.java b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/user/User.java new file mode 100644 index 00000000..5a260782 --- /dev/null +++ b/springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/user/User.java @@ -0,0 +1,46 @@ +package com.codingapi.springboot.flow.user; + +import lombok.Getter; +import lombok.Setter; + +@Getter +public class User implements IFlowOperator{ + + @Setter + private long id; + + private String name; + + private boolean isFlowManager; + + private User entrustOperator; + + public User(String name,boolean isFlowManager) { + this.name = name; + this.isFlowManager = isFlowManager; + } + + public User(String name,User entrustOperator) { + this.name = name; + this.entrustOperator = entrustOperator; + } + + public User(String name) { + this(name,false); + } + + @Override + public IFlowOperator entrustOperator() { + return entrustOperator; + } + + @Override + public long getUserId() { + return id; + } + + @Override + public boolean isFlowManager() { + return isFlowManager; + } +} diff --git a/springboot-starter-id-generator/pom.xml b/springboot-starter-id-generator/pom.xml deleted file mode 100644 index c4adbf85..00000000 --- a/springboot-starter-id-generator/pom.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - springboot-parent - com.codingapi.springboot - 2.1.11 - - 4.0.0 - - springboot-starter-id-generator - - - 17 - - - - - - org.xerial - sqlite-jdbc - - - - commons-dbutils - commons-dbutils - - - - org.springframework - spring-jdbc - - - - - \ No newline at end of file diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/AutoConfiguration.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/AutoConfiguration.java deleted file mode 100644 index e9a8b394..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/AutoConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.codingapi.springboot.generator; - -import com.codingapi.springboot.generator.dao.IdKeyDao; -import com.codingapi.springboot.generator.properties.GeneratorJdbcProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class AutoConfiguration { - - @Bean - @ConfigurationProperties(prefix = "codingapi.id.jdbc.generator") - public GeneratorJdbcProperties generatorJdbcProperties() { - return new GeneratorJdbcProperties(); - } - - @Bean(initMethod = "init") - @ConditionalOnMissingBean - public IdKeyDao idKeyDao(GeneratorJdbcProperties generatorJdbcProperties) { - IdKeyDao keyDao = new IdKeyDao(generatorJdbcProperties.openDataSource()); - IdGeneratorContext.getInstance().init(keyDao); - return keyDao; - } - -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGenerate.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGenerate.java deleted file mode 100644 index 972459a8..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGenerate.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.codingapi.springboot.generator; - -public interface IdGenerate { - - default long generateLongId() { - return IdGeneratorContext.getInstance().generateId(getClass()); - } - - default int generateIntId() { - return (int) IdGeneratorContext.getInstance().generateId(getClass()); - } - - default String generateStringId() { - return String.valueOf(IdGeneratorContext.getInstance().generateId(getClass())); - } - -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGeneratorContext.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGeneratorContext.java deleted file mode 100644 index c405ef30..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/IdGeneratorContext.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.codingapi.springboot.generator; - -import com.codingapi.springboot.generator.dao.IdKeyDao; -import com.codingapi.springboot.generator.service.IdGenerateService; - -public class IdGeneratorContext { - - private static IdGeneratorContext instance; - private IdGenerateService idGenerateService; - - private IdGeneratorContext() { - } - - public static IdGeneratorContext getInstance() { - if (instance == null) { - synchronized (IdGeneratorContext.class) { - if (instance == null) { - instance = new IdGeneratorContext(); - } - } - } - return instance; - } - - long generateId(Class clazz) { - return idGenerateService.generateId(clazz); - } - - void init(IdKeyDao idKeyDao) { - idGenerateService = new IdGenerateService(idKeyDao); - } - -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/DbHelper.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/DbHelper.java deleted file mode 100644 index 92b8191a..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/DbHelper.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.codingapi.springboot.generator.dao; - -import org.apache.commons.dbutils.QueryRunner; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.SQLException; - -public class DbHelper { - - private final DataSource dataSource; - private final QueryRunner queryRunner; - - - public DbHelper(DataSource dataSource) { - this.dataSource = dataSource; - this.queryRunner = new QueryRunner(); - } - - public void execute(IExecute execute) throws SQLException { - Connection connection = dataSource.getConnection(); - execute.execute(connection, queryRunner); - connection.close(); - } - - public T updateAndQuery(IUpdateAndQuery execute) throws SQLException { - Connection connection = dataSource.getConnection(); - connection.setAutoCommit(false); - T res = execute.updateAndQuery(connection, queryRunner); - connection.commit(); - connection.close(); - return res; - } - - public int update(IUpdate execute) throws SQLException { - Connection connection = dataSource.getConnection(); - connection.setAutoCommit(false); - int res = execute.update(connection, queryRunner); - connection.commit(); - connection.close(); - return res; - } - - public T query(IQuery execute) throws SQLException { - Connection connection = dataSource.getConnection(); - T res = execute.query(connection, queryRunner); - connection.close(); - return res; - } - - - interface IExecute { - default void execute(Connection connection, QueryRunner queryRunner) throws SQLException { - } - } - - - interface IUpdateAndQuery { - default T updateAndQuery(Connection connection, QueryRunner queryRunner) throws SQLException { - return null; - } - } - - interface IUpdate { - default int update(Connection connection, QueryRunner queryRunner) throws SQLException { - return 0; - } - } - - interface IQuery { - default T query(Connection connection, QueryRunner queryRunner) throws SQLException { - return null; - } - } - -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/IdKeyDao.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/IdKeyDao.java deleted file mode 100644 index 59332bc0..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/dao/IdKeyDao.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.codingapi.springboot.generator.dao; - -import com.codingapi.springboot.generator.domain.IdKey; -import lombok.SneakyThrows; -import org.apache.commons.dbutils.QueryRunner; -import org.apache.commons.dbutils.ResultSetHandler; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -public class IdKeyDao { - - private final DbHelper> dbHelper; - - private final ResultSetHandler> handler; - - public IdKeyDao(DataSource dataSource) { - this.dbHelper = new DbHelper<>(dataSource); - this.handler = rs -> { - List list = new ArrayList<>(); - while (rs.next()) { - IdKey generator = new IdKey(); - generator.setKey(rs.getString("TAG")); - generator.setId(rs.getInt("ID")); - generator.setUpdateTime(rs.getLong("UPDATE_TIME")); - list.add(generator); - } - return list; - }; - } - - @SneakyThrows - public void save(IdKey generator) { - dbHelper.update(new DbHelper.IUpdate() { - @Override - public int update(Connection connection, QueryRunner queryRunner) throws SQLException { - List list = queryRunner.query(connection, "SELECT * FROM ID_GENERATOR WHERE TAG = ?", handler, generator.getKey()); - if (list != null && list.size() > 0) { - return 0; - } - - String sql = "INSERT INTO ID_GENERATOR (ID, UPDATE_TIME, TAG) VALUES (?, ?, ?)"; - return queryRunner.update(connection, sql, generator.getId(), generator.getUpdateTime(), generator.getKey()); - } - }); - } - - - @SneakyThrows - public IdKey getByKey(String key) { - return dbHelper.query(new DbHelper.IQuery>() { - @Override - public List query(Connection connection, QueryRunner queryRunner) throws SQLException { - return queryRunner.query(connection, "SELECT * FROM ID_GENERATOR WHERE TAG = ?", handler, key); - } - }).stream().findFirst().orElse(null); - } - - - @SneakyThrows - public IdKey updateMaxId(IdKey generator) { - return dbHelper.updateAndQuery(new DbHelper.IUpdateAndQuery<>() { - @Override - public List updateAndQuery(Connection connection, QueryRunner queryRunner) throws SQLException { - queryRunner.update(connection, "UPDATE ID_GENERATOR SET ID = ID + 1 WHERE TAG = ?", generator.getKey()); - return queryRunner.query(connection, "SELECT * FROM ID_GENERATOR WHERE TAG = ?", handler, generator.getKey()); - } - }).stream().findFirst().orElse(null); - } - - - @SneakyThrows - public List findAll() throws SQLException { - return dbHelper.query(new DbHelper.IQuery<>() { - @Override - public List query(Connection connection, QueryRunner queryRunner) throws SQLException { - return queryRunner.query(connection, "SELECT * FROM ID_GENERATOR", handler); - } - }); - } - - private void init() throws SQLException { - dbHelper.execute(new DbHelper.IExecute() { - @Override - public void execute(Connection connection, QueryRunner queryRunner) throws SQLException { - String sql = "CREATE TABLE IF NOT EXISTS ID_GENERATOR (TAG TEXT NOT NULL, ID INTEGER NOT NULL, UPDATE_TIME INTEGER NOT NULL, PRIMARY KEY (TAG))"; - queryRunner.update(connection, sql); - } - }); - } -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/domain/IdKey.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/domain/IdKey.java deleted file mode 100644 index 0363383e..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/domain/IdKey.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.codingapi.springboot.generator.domain; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Setter -@Getter -@NoArgsConstructor -public class IdKey { - - private String key; - private long id; - private long updateTime; - - public IdKey(String key, long id) { - this.key = key; - this.id = id; - this.updateTime = System.currentTimeMillis(); - } - - public IdKey(String key) { - this.key = key; - this.id = 1; - this.updateTime = System.currentTimeMillis(); - } - - public IdKey(String key, long id, long updateTime) { - this.key = key; - this.id = id; - this.updateTime = updateTime; - } -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/properties/GeneratorJdbcProperties.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/properties/GeneratorJdbcProperties.java deleted file mode 100644 index 6d89224a..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/properties/GeneratorJdbcProperties.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.codingapi.springboot.generator.properties; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import javax.sql.DataSource; - -@Setter -@Getter -public class GeneratorJdbcProperties { - - private String jdbcUrl = "jdbc:sqlite:db.db"; - - private String jdbcDriver = "org.sqlite.JDBC"; - - private String jdbcUsername = "sa"; - - private String jdbcPassword = "sa"; - - public DataSource openDataSource() { - final DriverManagerDataSource dataSource = new DriverManagerDataSource(); - dataSource.setDriverClassName(jdbcDriver); - dataSource.setUrl(jdbcUrl); - dataSource.setUsername(jdbcUsername); - dataSource.setPassword(jdbcPassword); - return dataSource; - } -} diff --git a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/service/IdGenerateService.java b/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/service/IdGenerateService.java deleted file mode 100644 index 9d7dc478..00000000 --- a/springboot-starter-id-generator/src/main/java/com/codingapi/springboot/generator/service/IdGenerateService.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.codingapi.springboot.generator.service; - -import com.codingapi.springboot.generator.dao.IdKeyDao; -import com.codingapi.springboot.generator.domain.IdKey; - -public class IdGenerateService { - - private final IdKeyDao keyDao; - - public IdGenerateService(IdKeyDao keyDao) { - this.keyDao = keyDao; - } - - public synchronized long generateId(Class clazz) { - IdKey idKey = keyDao.getByKey(clazz.getName()); - if (idKey == null) { - idKey = new IdKey(clazz.getName()); - keyDao.save(idKey); - } else { - keyDao.updateMaxId(idKey); - } - return idKey.getId(); - } -} diff --git a/springboot-starter-id-generator/src/main/resources/META-INF/spring.factories b/springboot-starter-id-generator/src/main/resources/META-INF/spring.factories deleted file mode 100644 index f30445ef..00000000 --- a/springboot-starter-id-generator/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.codingapi.springboot.generator.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-id-generator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-id-generator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index c1aa4e07..00000000 --- a/springboot-starter-id-generator/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.codingapi.springboot.generator.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGenerateServiceTest.java b/springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGenerateServiceTest.java deleted file mode 100644 index a2176a7c..00000000 --- a/springboot-starter-id-generator/src/test/java/com/codingapi/springboot/generator/IdGenerateServiceTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.codingapi.springboot.generator; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Slf4j -@SpringBootTest -class IdGenerateServiceTest { - - @Test - void generateId() { - for (int i = 0; i < 1000; i++) { - long id = IdGeneratorContext.getInstance().generateId(IdGenerateServiceTest.class); - log.info("id=>{}", id); - assertTrue(id > 0, "id generator error."); - } - } -} \ No newline at end of file diff --git a/springboot-starter-id-generator/src/test/resources/application.properties b/springboot-starter-id-generator/src/test/resources/application.properties deleted file mode 100644 index 8a3b6d8a..00000000 --- a/springboot-starter-id-generator/src/test/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -codingapi.id.generator.jdbc-url=jdbc:h2:file:./test.db \ No newline at end of file diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java b/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java deleted file mode 100644 index 5ad14115..00000000 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.codingapi.springboot.security.filter; - -import com.codingapi.springboot.security.dto.request.LoginRequest; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public interface SecurityLoginHandler { - - void preHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest handler) throws Exception; -} diff --git a/springboot-starter-security-jwt/src/main/resources/META-INF/spring.factories b/springboot-starter-security-jwt/src/main/resources/META-INF/spring.factories deleted file mode 100644 index b7882c59..00000000 --- a/springboot-starter-security-jwt/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,4 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.codingapi.springboot.security.configurer.WebSecurityConfigurer,\ -com.codingapi.springboot.security.crypto.MyCryptoConfiguration,\ -com.codingapi.springboot.security.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-security-jwt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-security-jwt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 69b9af0d..00000000 --- a/springboot-starter-security-jwt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,3 +0,0 @@ -com.codingapi.springboot.security.configurer.WebSecurityConfigurer -com.codingapi.springboot.security.crypto.MyCryptoConfiguration -com.codingapi.springboot.security.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-security-jwt/pom.xml b/springboot-starter-security/pom.xml similarity index 71% rename from springboot-starter-security-jwt/pom.xml rename to springboot-starter-security/pom.xml index 6369c486..ffebe777 100644 --- a/springboot-starter-security-jwt/pom.xml +++ b/springboot-starter-security/pom.xml @@ -6,16 +6,16 @@ springboot-parent com.codingapi.springboot - 2.1.11 + 2.10.5 - springboot-starter-security-jwt + springboot-starter-security - springboot-starter-security-jwt - springboot-starter-security-jwt project for Spring Boot + springboot-starter-security + springboot-starter-security project for Spring Boot - 17 + 8 @@ -30,19 +30,28 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-redis + provided + + io.jsonwebtoken jjwt-api + provided io.jsonwebtoken jjwt-impl + provided io.jsonwebtoken jjwt-jackson + provided diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java similarity index 63% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java index ba56c691..b8b21a76 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/AutoConfiguration.java @@ -2,9 +2,14 @@ import com.codingapi.springboot.security.configurer.HttpSecurityConfigurer; import com.codingapi.springboot.security.controller.VersionController; +import com.codingapi.springboot.security.customer.DefaultHttpSecurityCustomer; +import com.codingapi.springboot.security.customer.HttpSecurityCustomer; +import com.codingapi.springboot.security.dto.request.LoginRequest; +import com.codingapi.springboot.security.dto.response.LoginResponse; import com.codingapi.springboot.security.filter.*; -import com.codingapi.springboot.security.jwt.Jwt; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenGateway; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -24,6 +29,9 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + @Configuration @EnableWebSecurity public class AutoConfiguration { @@ -53,28 +61,53 @@ public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } + @Bean + @ConditionalOnMissingBean + public AuthenticationTokenFilter authenticationTokenFilter() { + return (request, response) -> { + + }; + } @Bean @ConditionalOnMissingBean - public SecurityLoginHandler securityLoginHandler(){ - return (request, response, handler) -> { + public HttpSecurityCustomer httpSecurityCustomer(CodingApiSecurityProperties properties){ + return new DefaultHttpSecurityCustomer(properties); + } + + @Bean + @ConditionalOnMissingBean + public SecurityLoginHandler securityLoginHandler() { + return new SecurityLoginHandler() { + @Override + public void preHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest handler) throws Exception { + + } + + @Override + public LoginResponse postHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest loginRequest,UserDetails userDetails, Token token) { + LoginResponse loginResponse = new LoginResponse(); + loginResponse.setUsername(token.getUsername()); + loginResponse.setToken(token.getToken()); + loginResponse.setAuthorities(token.getAuthorities()); + return loginResponse; + } }; } @Bean @ConditionalOnMissingBean - public SecurityFilterChain filterChain(HttpSecurity security, Jwt jwt,SecurityLoginHandler loginHandler, - SecurityJwtProperties properties) throws Exception { - //disable basic auth - security.httpBasic().disable(); - - //before add addCorsMappings to enable cors. - security.cors(); - if(properties.isDisableCsrf() ){ - security.csrf().disable(); - } - security.apply(new HttpSecurityConfigurer(jwt,loginHandler,properties)); + public SecurityFilterChain filterChain(HttpSecurity security, + HttpSecurityCustomer httpSecurityCustomer, + TokenGateway tokenGateway, + SecurityLoginHandler loginHandler, + CodingApiSecurityProperties properties, + AuthenticationTokenFilter authenticationTokenFilter) throws Exception { + + httpSecurityCustomer.customize(security); + + security.apply(new HttpSecurityConfigurer(tokenGateway, loginHandler, properties, authenticationTokenFilter)); security .exceptionHandling() .authenticationEntryPoint(new MyUnAuthenticationEntryPoint()) @@ -82,8 +115,8 @@ public SecurityFilterChain filterChain(HttpSecurity security, Jwt jwt,SecurityLo .and() .authorizeHttpRequests( registry -> { - registry.requestMatchers(properties.getIgnoreUrls()).permitAll() - .requestMatchers(properties.getAuthenticatedUrls()).authenticated() + registry.antMatchers(properties.getIgnoreUrls()).permitAll() + .antMatchers(properties.getAuthenticatedUrls()).authenticated() .anyRequest().permitAll(); } ) @@ -113,19 +146,13 @@ public AuthenticationProvider authenticationProvider(UserDetailsService userDeta } - @Bean - @ConditionalOnMissingBean - public Jwt jwt(SecurityJwtProperties properties) { - return new Jwt(properties.getJwtSecretKey(), properties.getJwtTime(), properties.getJwtRestTime()); - } - @Bean - public WebMvcConfigurer corsConfigurer(SecurityJwtProperties securityJwtProperties) { + public WebMvcConfigurer corsConfigurer(CodingApiSecurityProperties securityProperties) { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { - if(securityJwtProperties.isDisableCors()) { + if (securityProperties.isDisableCors()) { registry.addMapping("/**") .allowedHeaders("*") .allowedMethods("*") @@ -142,14 +169,14 @@ public void addCorsMappings(CorsRegistry registry) { @Bean @ConfigurationProperties(prefix = "codingapi.security") - public SecurityJwtProperties securityJwtProperties() { - return new SecurityJwtProperties(); + public CodingApiSecurityProperties codingApiSecurityProperties() { + return new CodingApiSecurityProperties(); } @Bean @ConditionalOnMissingBean - public VersionController versionController(Environment environment){ + public VersionController versionController(Environment environment) { return new VersionController(environment); } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java similarity index 63% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java index d46a3a0b..e6258f1c 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/HttpSecurityConfigurer.java @@ -1,10 +1,11 @@ package com.codingapi.springboot.security.configurer; +import com.codingapi.springboot.security.filter.AuthenticationTokenFilter; import com.codingapi.springboot.security.filter.MyAuthenticationFilter; import com.codingapi.springboot.security.filter.MyLoginFilter; import com.codingapi.springboot.security.filter.SecurityLoginHandler; -import com.codingapi.springboot.security.jwt.Jwt; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; +import com.codingapi.springboot.security.gateway.TokenGateway; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import lombok.AllArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -13,15 +14,16 @@ @AllArgsConstructor public class HttpSecurityConfigurer extends AbstractHttpConfigurer { - private final Jwt jwt; + private final TokenGateway tokenGateway; private final SecurityLoginHandler securityLoginHandler; - private final SecurityJwtProperties securityJwtProperties; + private final CodingApiSecurityProperties securityProperties; + private final AuthenticationTokenFilter authenticationTokenFilter; @Override public void configure(HttpSecurity security) throws Exception { AuthenticationManager manager = security.getSharedObject(AuthenticationManager.class); - security.addFilter(new MyLoginFilter(manager, jwt,securityLoginHandler, securityJwtProperties)); - security.addFilter(new MyAuthenticationFilter(manager,securityJwtProperties,jwt)); + security.addFilter(new MyLoginFilter(manager, tokenGateway,securityLoginHandler, securityProperties)); + security.addFilter(new MyAuthenticationFilter(manager,securityProperties,tokenGateway,authenticationTokenFilter)); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java similarity index 71% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java index 7e5f3e75..06d84d7c 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/configurer/WebSecurityConfigurer.java @@ -1,6 +1,6 @@ package com.codingapi.springboot.security.configurer; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import lombok.AllArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.WebSecurity; @@ -10,12 +10,12 @@ @AllArgsConstructor public class WebSecurityConfigurer implements WebSecurityCustomizer { - private final SecurityJwtProperties securityJwtProperties; + private final CodingApiSecurityProperties securityProperties; @Override public void customize(WebSecurity web) { //ignoring security filters request url - web.ignoring().requestMatchers(securityJwtProperties.getIgnoreUrls()); + web.ignoring().antMatchers(securityProperties.getIgnoreUrls()); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/controller/VersionController.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/controller/VersionController.java similarity index 71% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/controller/VersionController.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/controller/VersionController.java index 7d2385a8..03e8445d 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/controller/VersionController.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/controller/VersionController.java @@ -1,5 +1,6 @@ package com.codingapi.springboot.security.controller; +import com.codingapi.springboot.framework.dto.response.SingleResponse; import lombok.AllArgsConstructor; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; @@ -14,7 +15,7 @@ public class VersionController { private final Environment env; @GetMapping("/version") - public String version(){ - return env.getProperty("application.version","-"); + public SingleResponse version() { + return SingleResponse.of(env.getProperty("application.version", "-")); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyAES.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/AESTools.java similarity index 84% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyAES.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/AESTools.java index b3536ca8..efe4a7a8 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyAES.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/AESTools.java @@ -6,20 +6,20 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; -public class MyAES { +public class AESTools { - private final static MyAES instance = new MyAES(); + private final static AESTools instance = new AESTools(); private AES aes; - private MyAES() { + private AESTools() { } void init(AES aes) { this.aes = aes; } - public static MyAES getInstance() { + public static AESTools getInstance() { return instance; } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyCryptoConfiguration.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/SecurityCryptoConfiguration.java similarity index 69% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyCryptoConfiguration.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/SecurityCryptoConfiguration.java index 0d4b2bd4..6820dc5c 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/crypto/MyCryptoConfiguration.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/crypto/SecurityCryptoConfiguration.java @@ -1,7 +1,7 @@ package com.codingapi.springboot.security.crypto; import com.codingapi.springboot.framework.crypto.AES; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,15 +9,14 @@ import java.util.Base64; @Configuration -public class MyCryptoConfiguration { +public class SecurityCryptoConfiguration { @Bean @ConditionalOnMissingBean - public AES aes(SecurityJwtProperties properties) throws Exception { + public AES aes(CodingApiSecurityProperties properties) throws Exception { AES aes = new AES(Base64.getDecoder().decode(properties.getAseKey().getBytes()), Base64.getDecoder().decode(properties.getAseIv())); - MyAES.getInstance().init(aes); + AESTools.getInstance().init(aes); return aes; } - } diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java new file mode 100644 index 00000000..02733c6f --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/DefaultHttpSecurityCustomer.java @@ -0,0 +1,50 @@ +package com.codingapi.springboot.security.customer; + +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; +import lombok.AllArgsConstructor; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; + +@AllArgsConstructor +public class DefaultHttpSecurityCustomer implements HttpSecurityCustomer { + + private final CodingApiSecurityProperties properties; + + @Override + public void customize(HttpSecurity security) throws Exception { + //disable basic auth + if (properties.isDisableBasicAuth()) { + security.httpBasic(AbstractHttpConfigurer::disable); + } + + //disable frame options + if (properties.isDisableFrameOptions()) { + security.headers(new Customizer>() { + @Override + public void customize(HeadersConfigurer httpSecurityHeadersConfigurer) { + httpSecurityHeadersConfigurer.frameOptions(new Customizer.FrameOptionsConfig>() { + @Override + public void customize(HeadersConfigurer.FrameOptionsConfig frameOptionsConfig) { + frameOptionsConfig.disable(); + } + }); + } + }); + } + + //before add addCorsMappings to enable cors. + security.cors(httpSecurityCorsConfigurer -> { + if (properties.isDisableCors()) { + httpSecurityCorsConfigurer.disable(); + } + }); + + security.csrf(httpSecurityCsrfConfigurer -> { + if (properties.isDisableCsrf()) { + httpSecurityCsrfConfigurer.disable(); + } + }); + } +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java new file mode 100644 index 00000000..ddc35dd6 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/customer/HttpSecurityCustomer.java @@ -0,0 +1,9 @@ +package com.codingapi.springboot.security.customer; + +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +public interface HttpSecurityCustomer { + + void customize(HttpSecurity security) throws Exception; + +} diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequest.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequest.java similarity index 100% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequest.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequest.java diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequestContext.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequestContext.java similarity index 100% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequestContext.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/request/LoginRequestContext.java diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java similarity index 91% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java index cdfc3a3f..d717d36e 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/dto/response/LoginResponse.java @@ -12,5 +12,6 @@ public class LoginResponse { private String username; private String token; private List authorities; + private Object data; } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/exception/TokenExpiredException.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/exception/TokenExpiredException.java similarity index 100% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/exception/TokenExpiredException.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/exception/TokenExpiredException.java diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/AuthenticationTokenFilter.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/AuthenticationTokenFilter.java new file mode 100644 index 00000000..020ea664 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/AuthenticationTokenFilter.java @@ -0,0 +1,13 @@ +package com.codingapi.springboot.security.filter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public interface AuthenticationTokenFilter { + + + void doFilter(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException; + +} diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java similarity index 74% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java index 191d1dfc..beb79203 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAccessDeniedHandler.java @@ -7,9 +7,9 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -20,6 +20,10 @@ public class MyAccessDeniedHandler implements AccessDeniedHandler { public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { log.debug("access denied"); String content = JSONObject.toJSONString(Response.buildFailure("not.access", "please check user authentication.")); + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + IOUtils.write(content, response.getOutputStream(), StandardCharsets.UTF_8); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java similarity index 63% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java index 09562919..232fd938 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyAuthenticationFilter.java @@ -3,13 +3,9 @@ import com.alibaba.fastjson.JSONObject; import com.codingapi.springboot.framework.dto.response.Response; import com.codingapi.springboot.security.exception.TokenExpiredException; -import com.codingapi.springboot.security.jwt.Jwt; -import com.codingapi.springboot.security.jwt.Token; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenGateway; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.security.authentication.AuthenticationManager; @@ -17,8 +13,11 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.util.AntPathMatcher; import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -27,22 +26,24 @@ public class MyAuthenticationFilter extends BasicAuthenticationFilter { private final static String TOKEN_KEY = "Authorization"; - private final Jwt jwt; + private final TokenGateway tokenGateway; - private final SecurityJwtProperties securityJwtProperties; + private final CodingApiSecurityProperties securityProperties; + private final AuthenticationTokenFilter authenticationTokenFilter; private final AntPathMatcher antPathMatcher = new AntPathMatcher(); - public MyAuthenticationFilter(AuthenticationManager manager, SecurityJwtProperties securityJwtProperties, Jwt jwt) { + public MyAuthenticationFilter(AuthenticationManager manager, CodingApiSecurityProperties securityProperties, TokenGateway tokenGateway,AuthenticationTokenFilter authenticationTokenFilter) { super(manager); - this.jwt = jwt; - this.securityJwtProperties = securityJwtProperties; + this.tokenGateway = tokenGateway; + this.securityProperties = securityProperties; + this.authenticationTokenFilter = authenticationTokenFilter; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { log.debug("token authentication ~"); - for (String antUrl : securityJwtProperties.getAuthenticatedUrls()) { + for (String antUrl : securityProperties.getAuthenticatedUrls()) { if(antPathMatcher.match(antUrl,request.getRequestURI())) { String sign = request.getHeader(TOKEN_KEY); @@ -51,9 +52,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - Token token = jwt.parser(sign); + Token token = tokenGateway.parser(sign); if (token.canRestToken()) { - Token newSign = jwt.create(token.getUsername(), token.decodeIv(), token.getAuthorities(), token.getExtra()); + Token newSign = tokenGateway.create(token.getUsername(), token.decodeIv(), token.getAuthorities(), token.getExtra()); log.info("reset token "); response.setHeader(TOKEN_KEY, newSign.getToken()); } @@ -65,6 +66,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } SecurityContextHolder.getContext().setAuthentication(token.getAuthenticationToken()); + authenticationTokenFilter.doFilter(request, response); } } chain.doFilter(request, response); @@ -73,6 +75,10 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private void writeResponse(HttpServletResponse servletResponse, Response returnResponse) throws IOException { String content = JSONObject.toJSONString(returnResponse); + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + servletResponse.setContentType("application/json;charset=UTF-8"); + servletResponse.setCharacterEncoding("UTF-8"); + IOUtils.write(content, servletResponse.getOutputStream(), StandardCharsets.UTF_8); } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java similarity index 71% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java index f472ed99..c0ecc62b 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLoginFilter.java @@ -6,10 +6,10 @@ import com.codingapi.springboot.security.dto.request.LoginRequest; import com.codingapi.springboot.security.dto.request.LoginRequestContext; import com.codingapi.springboot.security.dto.response.LoginResponse; -import com.codingapi.springboot.security.jwt.Jwt; -import com.codingapi.springboot.security.jwt.Token; -import com.codingapi.springboot.security.jwt.TokenContext; -import com.codingapi.springboot.security.properties.SecurityJwtProperties; +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenContext; +import com.codingapi.springboot.security.gateway.TokenGateway; +import com.codingapi.springboot.security.properties.CodingApiSecurityProperties; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.security.authentication.AuthenticationManager; @@ -18,14 +18,14 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; @@ -33,15 +33,15 @@ @Slf4j public class MyLoginFilter extends UsernamePasswordAuthenticationFilter { - private final Jwt jwt; + private final TokenGateway tokenGateway; private final SecurityLoginHandler loginHandler; - public MyLoginFilter(AuthenticationManager authenticationManager, Jwt jwt, SecurityLoginHandler loginHandler, SecurityJwtProperties securityJwtProperties) { + public MyLoginFilter(AuthenticationManager authenticationManager, TokenGateway tokenGateway, SecurityLoginHandler loginHandler, CodingApiSecurityProperties securityProperties) { super(authenticationManager); - this.jwt = jwt; + this.tokenGateway = tokenGateway; this.loginHandler = loginHandler; - this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(securityJwtProperties.getLoginProcessingUrl(), "POST")); + this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(securityProperties.getLoginProcessingUrl(), "POST")); } @Override @@ -58,7 +58,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ throw new AuthenticationServiceException("request stream read was null."); } try { - loginHandler.preHandle(request,response,login); + loginHandler.preHandle(request, response, login); } catch (Exception e) { throw new AuthenticationServiceException(e.getLocalizedMessage()); } @@ -69,19 +69,21 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { log.debug("login success authentication ~"); - User user = (User) authResult.getPrincipal(); + UserDetails user = (UserDetails) authResult.getPrincipal(); LoginRequest loginRequest = LoginRequestContext.getInstance().get(); - Token token = jwt.create(user.getUsername(), loginRequest.getPassword(), + Token token = tokenGateway.create(user.getUsername(), loginRequest.getPassword(), user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()), TokenContext.getExtra()); - LoginResponse login = new LoginResponse(); - login.setUsername(user.getUsername()); - login.setToken(token.getToken()); - login.setAuthorities(token.getAuthorities()); + LoginResponse loginResponse = loginHandler.postHandle(request, response, loginRequest, user, token); + + String content = JSONObject.toJSONString(SingleResponse.of(loginResponse)); + + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); - String content = JSONObject.toJSONString(SingleResponse.of(login)); IOUtils.write(content, response.getOutputStream(), StandardCharsets.UTF_8); LoginRequestContext.getInstance().clean(); @@ -92,7 +94,13 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { log.debug("login fail authentication ~"); String content = JSONObject.toJSONString(Response.buildFailure("login.error", failed.getMessage())); + + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + IOUtils.write(content, response.getOutputStream(), StandardCharsets.UTF_8); + LoginRequestContext.getInstance().clean(); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java similarity index 82% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java index a27c9d76..b2cbe8d2 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutHandler.java @@ -4,8 +4,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + @Slf4j public class MyLogoutHandler implements LogoutHandler { diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java similarity index 73% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java index 16a86c68..5b6520a6 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyLogoutSuccessHandler.java @@ -7,9 +7,9 @@ import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -20,6 +20,10 @@ public class MyLogoutSuccessHandler implements LogoutSuccessHandler { public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.debug("logout success ~"); String content = JSONObject.toJSONString(Response.buildSuccess()); + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + IOUtils.write(content, response.getOutputStream(), StandardCharsets.UTF_8); } } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java similarity index 73% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java index ecc91b95..885b6241 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/MyUnAuthenticationEntryPoint.java @@ -7,9 +7,10 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -20,6 +21,10 @@ public class MyUnAuthenticationEntryPoint implements AuthenticationEntryPoint { public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { log.debug("no authentication ~"); String content = JSONObject.toJSONString(Response.buildFailure("not.login", "please to login.")); + // 设置响应的 Content-Type 为 JSON,并指定字符编码为 UTF-8 + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + IOUtils.write(content, response.getOutputStream(), StandardCharsets.UTF_8); } } diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java new file mode 100644 index 00000000..db7bc687 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/filter/SecurityLoginHandler.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.security.filter; + +import com.codingapi.springboot.security.dto.request.LoginRequest; +import com.codingapi.springboot.security.dto.response.LoginResponse; +import com.codingapi.springboot.security.gateway.Token; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public interface SecurityLoginHandler { + + void preHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest loginRequest) throws Exception; + + LoginResponse postHandle(HttpServletRequest request, HttpServletResponse response, LoginRequest loginRequest, UserDetails userDetails, Token token); +} diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Token.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/Token.java similarity index 91% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Token.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/Token.java index a3fd6345..9579a9b3 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Token.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/Token.java @@ -1,8 +1,8 @@ -package com.codingapi.springboot.security.jwt; +package com.codingapi.springboot.security.gateway; import com.alibaba.fastjson.JSONObject; import com.codingapi.springboot.framework.serializable.JsonSerializable; -import com.codingapi.springboot.security.crypto.MyAES; +import com.codingapi.springboot.security.crypto.AESTools; import com.codingapi.springboot.security.exception.TokenExpiredException; import lombok.Getter; import lombok.NoArgsConstructor; @@ -34,7 +34,7 @@ public Token(String username, String iv,String extra, List authorities, this.username = username; this.extra = extra; if(iv!=null) { - this.iv = MyAES.getInstance().encode(iv); + this.iv = AESTools.getInstance().encode(iv); } this.authorities = authorities; this.expireTime = System.currentTimeMillis() + expireValue; @@ -56,7 +56,7 @@ public String decodeIv(){ if(iv==null){ return null; } - return MyAES.getInstance().decode(iv); + return AESTools.getInstance().decode(iv); } diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/TokenContext.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenContext.java similarity index 91% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/TokenContext.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenContext.java index 99b37a7c..e3ecaa8f 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/TokenContext.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenContext.java @@ -1,4 +1,4 @@ -package com.codingapi.springboot.security.jwt; +package com.codingapi.springboot.security.gateway; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenGateway.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenGateway.java new file mode 100644 index 00000000..b339079d --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/gateway/TokenGateway.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.security.gateway; + +import java.util.List; + +public interface TokenGateway { + + Token create(String username, String iv, List authorities, String extra); + + default Token create(String username, String iv, List authorities) { + return create(username, iv, authorities, null); + } + + default Token create(String username, List authorities) { + return create(username, null, authorities, null); + } + + default Token create(String username, List authorities, String extra) { + return create(username, null, authorities, extra); + } + + Token parser(String sign); + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTSecurityConfiguration.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTSecurityConfiguration.java new file mode 100644 index 00000000..54df5b3c --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTSecurityConfiguration.java @@ -0,0 +1,35 @@ +package com.codingapi.springboot.security.jwt; + +import com.codingapi.springboot.security.gateway.TokenGateway; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnProperty(prefix = "codingapi.security.jwt", name = "enable", havingValue = "true") +public class JWTSecurityConfiguration { + + + @Bean + @ConfigurationProperties(prefix = "codingapi.security.jwt") + public SecurityJWTProperties securityJWTProperties() { + return new SecurityJWTProperties(); + } + + + @Bean + @ConditionalOnMissingBean + public JwtTokenGateway jwtTokenGateway(SecurityJWTProperties properties) { + return new JwtTokenGateway(properties); + } + + + @Bean + @ConditionalOnMissingBean + public TokenGateway jwtTokenGatewayImpl(JwtTokenGateway jwtTokenGateway) { + return new JWTTokenGatewayImpl(jwtTokenGateway); + } + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTTokenGatewayImpl.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTTokenGatewayImpl.java new file mode 100644 index 00000000..4a890ab7 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JWTTokenGatewayImpl.java @@ -0,0 +1,25 @@ +package com.codingapi.springboot.security.jwt; + +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenGateway; + +import java.util.List; + +public class JWTTokenGatewayImpl implements TokenGateway { + + private final JwtTokenGateway jwtTokenGateway; + + public JWTTokenGatewayImpl(JwtTokenGateway jwtTokenGateway) { + this.jwtTokenGateway = jwtTokenGateway; + } + + @Override + public Token create(String username, String password, List authorities, String extra) { + return jwtTokenGateway.create(username, password, authorities, extra); + } + + @Override + public Token parser(String sign) { + return jwtTokenGateway.parser(sign); + } +} diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Jwt.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JwtTokenGateway.java similarity index 64% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Jwt.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JwtTokenGateway.java index c1b7a2b8..57dab485 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/jwt/Jwt.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/JwtTokenGateway.java @@ -2,28 +2,29 @@ import com.alibaba.fastjson.JSONObject; import com.codingapi.springboot.framework.exception.LocaleMessageException; +import com.codingapi.springboot.security.gateway.Token; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; +import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; -import java.security.Key; import java.util.List; -public class Jwt { +public class JwtTokenGateway { - private final Key key; - private final int jwtTime; - private final int jwtRestTime; + private final SecretKey key; + private final int validTime; + private final int restTime; - public Jwt(String secretKey, int jwtTime, int jwtRestTime) { - this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); - this.jwtTime = jwtTime; - this.jwtRestTime = jwtRestTime; + public JwtTokenGateway(SecurityJWTProperties properties) { + this.key = Keys.hmacShaKeyFor(properties.getSecretKey().getBytes(StandardCharsets.UTF_8)); + this.validTime = properties.getValidTime(); + this.restTime = properties.getRestTime(); } - public Token create(String username, List authorities,String extra){ + public Token create(String username, List authorities, String extra){ return create(username, null,authorities, extra); } @@ -36,17 +37,17 @@ public Token create(String username, String iv, List authorities){ } public Token create(String username, String iv,List authorities,String extra){ - Token token = new Token(username, iv,extra, authorities, jwtTime, jwtRestTime); - String jwt = Jwts.builder().setSubject(token.toJson()).signWith(key).compact(); + Token token = new Token(username, iv,extra, authorities, validTime, restTime); + String jwt = Jwts.builder().subject(token.toJson()).signWith(key).compact(); token.setToken(jwt); return token; } public Token parser(String sign) { try { - Jws jws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(sign); + Jws jws = Jwts.parser().verifyWith(key).build().parseSignedClaims(sign); if (jws != null) { - String subject = jws.getBody().getSubject(); + String subject = jws.getPayload().getSubject(); return JSONObject.parseObject(subject, Token.class); } throw new LocaleMessageException("token.error", "token失效,请重新登录."); diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/SecurityJWTProperties.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/SecurityJWTProperties.java new file mode 100644 index 00000000..d4ca0537 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/jwt/SecurityJWTProperties.java @@ -0,0 +1,35 @@ +package com.codingapi.springboot.security.jwt; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class SecurityJWTProperties { + + + /** + * 是否启用JWT + */ + private boolean enable = true; + + /** + * JWT密钥 + * 需大于32位的字符串 + */ + private String secretKey = "codingapi.security.jwt.secretkey"; + + + /** + * JWT 有效时间(毫秒) + * 15分钟有效期 1000*60*15=900000 + */ + private int validTime = 900000; + + /** + * JWT 更换令牌时间(毫秒) + * 10分钟后更换令牌 1000*60*10=600000 + */ + private int restTime = 600000; + +} diff --git a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/properties/SecurityJwtProperties.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java similarity index 72% rename from springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/properties/SecurityJwtProperties.java rename to springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java index 5c296e80..0db1dad0 100644 --- a/springboot-starter-security-jwt/src/main/java/com/codingapi/springboot/security/properties/SecurityJwtProperties.java +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/properties/CodingApiSecurityProperties.java @@ -5,58 +5,48 @@ @Setter @Getter -public class SecurityJwtProperties { +public class CodingApiSecurityProperties { /** - * JWT密钥 - * 需大于32位的字符串 + * 权限拦截URL */ - private String jwtSecretKey = "codingapi.security.jwt.secretkey"; - + private String authenticatedUrls = "/api/**"; - /** - * aes key - */ - private String aseKey = "QUNEWCQlXiYqJCNYQ1phc0FDRFgkJV4mKiQjWENaYXM="; /** - * aes iv + * 登录接口地址 */ - private String aseIv = "QUNYRkdIQEVEUyNYQ1phcw=="; - + private String loginProcessingUrl = "/user/login"; /** - * JWT 有效时间(毫秒) - * 15分钟有效期 1000*60*15=900000 + * 退出登录接口地址 */ - private int jwtTime = 900000; + private String logoutUrl = "/user/logout"; /** - * JWT 更换令牌时间(毫秒) - * 10分钟后更换令牌 1000*60*10=600000 + * 放开接口地址 */ - private int jwtRestTime = 600000; + private String ignoreUrls = "/open/**"; /** - * 权限拦截URL + * aes key */ - private String authenticatedUrls = "/api/**"; - + private String aseKey = "QUNEWCQlXiYqJCNYQ1phc0FDRFgkJV4mKiQjWENaYXM="; /** - * 登录接口地址 + * aes iv */ - private String loginProcessingUrl = "/user/login"; + private String aseIv = "QUNYRkdIQEVEUyNYQ1phcw=="; /** - * 退出登录接口地址 + * 禁用Basic Auth */ - private String logoutUrl = "/user/logout"; + private boolean disableBasicAuth = true; /** - * 放开接口地址 + * 禁用FrameOptions */ - private String ignoreUrls = "/open/**"; + private boolean disableFrameOptions = true; /** * 启用禁用CSRF diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisSecurityConfiguration.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisSecurityConfiguration.java new file mode 100644 index 00000000..c4bf141b --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisSecurityConfiguration.java @@ -0,0 +1,35 @@ +package com.codingapi.springboot.security.redis; + +import com.codingapi.springboot.security.gateway.TokenGateway; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +@ConditionalOnProperty(prefix = "codingapi.security.redis", name = "enable", havingValue = "true") +public class RedisSecurityConfiguration { + + + @Bean + @ConfigurationProperties(prefix = "codingapi.security.redis") + public SecurityRedisProperties securityRedisProperties() { + return new SecurityRedisProperties(); + } + + + @Bean + @ConditionalOnMissingBean + public RedisTokenGateway redisTokenGateway(RedisTemplate redisTemplate, SecurityRedisProperties properties) { + return new RedisTokenGateway(redisTemplate, properties); + } + + @Bean + @ConditionalOnMissingBean + public TokenGateway redisTokenGatewayImpl(RedisTokenGateway redisTokenGateway) { + return new RedisTokenGatewayImpl(redisTokenGateway); + } + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGateway.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGateway.java new file mode 100644 index 00000000..0e7d69f0 --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGateway.java @@ -0,0 +1,110 @@ +package com.codingapi.springboot.security.redis; + +import com.alibaba.fastjson2.JSONObject; +import com.codingapi.springboot.security.gateway.Token; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +public class RedisTokenGateway { + + private final RedisTemplate redisTemplate; + private final int validTime; + private final int restTime; + + public RedisTokenGateway(RedisTemplate redisTemplate, SecurityRedisProperties properties) { + this.redisTemplate = redisTemplate; + this.validTime = properties.getValidTime(); + this.restTime = properties.getRestTime(); + } + + public Token create(String username, String iv, List authorities, String extra) { + Token token = new Token(username, iv, extra, authorities, validTime, restTime); + String key = String.format("%s:%s", username, UUID.randomUUID().toString().replaceAll("-", "")); + token.setToken(key); + redisTemplate.opsForValue().set(key, token.toJson(), validTime, TimeUnit.MILLISECONDS); + return token; + } + + /** + * 根据token获取用户信息 + * + * @param token token + * @return 用户信息 + */ + public Token getToken(String token) { + String json = redisTemplate.opsForValue().get(token); + if (json == null) { + return null; + } + return JSONObject.parseObject(json, Token.class); + } + + /** + * 删除token + * + * @param token token + */ + public void removeToken(String token) { + redisTemplate.delete(token); + } + + /** + * 重置token + * + * @param token token + */ + public void resetToken(Token token) { + redisTemplate.opsForValue().set(token.getToken(), token.toJson(), validTime, TimeUnit.MILLISECONDS); + } + + /** + * 删除用户 + * + * @param username 用户名 + */ + public void removeUsername(String username) { + Set keys = redisTemplate.keys(username + ":*"); + if (!keys.isEmpty()) { + redisTemplate.delete(keys); + } + } + + + /** + * 获取用户的所有token + * + * @param username 用户名 + * @return token列表 + */ + public List getTokensByUsername(String username) { + Set keys = redisTemplate.keys(username + ":*"); + return new ArrayList<>(keys); + } + + + /** + * 自定义删除用户 + * + * @param username 用户名 + * @param predicate 条件 + */ + public void removeUsername(String username, Predicate predicate) { + Set keys = redisTemplate.keys(username + ":*"); + if (!keys.isEmpty()) { + for (String key : keys) { + Token token = getToken(key); + if (token != null && predicate.test(token)) { + redisTemplate.delete(key); + } + } + } + } + + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGatewayImpl.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGatewayImpl.java new file mode 100644 index 00000000..8973f26d --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/RedisTokenGatewayImpl.java @@ -0,0 +1,26 @@ +package com.codingapi.springboot.security.redis; + +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenGateway; + +import java.util.List; + +public class RedisTokenGatewayImpl implements TokenGateway { + + private final RedisTokenGateway redisTokenGateway; + + public RedisTokenGatewayImpl(RedisTokenGateway redisTokenGateway) { + this.redisTokenGateway = redisTokenGateway; + } + + @Override + public Token create(String username, String iv, List authorities, String extra) { + return redisTokenGateway.create(username, iv, authorities, extra); + } + + @Override + public Token parser(String sign) { + return redisTokenGateway.getToken(sign); + } + +} diff --git a/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/SecurityRedisProperties.java b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/SecurityRedisProperties.java new file mode 100644 index 00000000..14eae9ac --- /dev/null +++ b/springboot-starter-security/src/main/java/com/codingapi/springboot/security/redis/SecurityRedisProperties.java @@ -0,0 +1,27 @@ +package com.codingapi.springboot.security.redis; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class SecurityRedisProperties { + + + /** + * 是否启用redis + */ + private boolean enable = true; + + /** + * 15分钟有效期 1000*60*15=900000 + */ + private int validTime = 900000; + + /** + * 10分钟后更换令牌 1000*60*10=600000 + */ + private int restTime = 600000; + + +} diff --git a/springboot-starter-security/src/main/resources/META-INF/spring.factories b/springboot-starter-security/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..6fbb976a --- /dev/null +++ b/springboot-starter-security/src/main/resources/META-INF/spring.factories @@ -0,0 +1,6 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.codingapi.springboot.security.configurer.WebSecurityConfigurer,\ +com.codingapi.springboot.security.crypto.SecurityCryptoConfiguration,\ +com.codingapi.springboot.security.jwt.JWTSecurityConfiguration,\ +com.codingapi.springboot.security.redis.RedisSecurityConfiguration,\ +com.codingapi.springboot.security.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..59324754 --- /dev/null +++ b/springboot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,5 @@ +com.codingapi.springboot.security.configurer.WebSecurityConfigurer +com.codingapi.springboot.security.crypto.SecurityCryptoConfiguration +com.codingapi.springboot.security.jwt.JWTSecurityConfiguration +com.codingapi.springboot.security.redis.RedisSecurityConfiguration +com.codingapi.springboot.security.AutoConfiguration \ No newline at end of file diff --git a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/SecurityJwtApplication.java b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/SecurityJwtApplication.java similarity index 100% rename from springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/SecurityJwtApplication.java rename to springboot-starter-security/src/test/java/com/codingapi/springboot/security/SecurityJwtApplication.java diff --git a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/SecurityJwtApplicationTest.java b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/SecurityJwtApplicationTest.java similarity index 100% rename from springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/SecurityJwtApplicationTest.java rename to springboot-starter-security/src/test/java/com/codingapi/springboot/security/SecurityJwtApplicationTest.java diff --git a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/controller/DemoController.java b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/controller/DemoController.java similarity index 100% rename from springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/controller/DemoController.java rename to springboot-starter-security/src/test/java/com/codingapi/springboot/security/controller/DemoController.java diff --git a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/jwt/TestVO.java b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/jwt/TestVO.java similarity index 100% rename from springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/jwt/TestVO.java rename to springboot-starter-security/src/test/java/com/codingapi/springboot/security/jwt/TestVO.java diff --git a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java similarity index 73% rename from springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java rename to springboot-starter-security/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java index 3010f7fb..b5611674 100644 --- a/springboot-starter-security-jwt/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java +++ b/springboot-starter-security/src/test/java/com/codingapi/springboot/security/jwt/TokenTest.java @@ -1,6 +1,8 @@ package com.codingapi.springboot.security.jwt; import com.codingapi.springboot.security.exception.TokenExpiredException; +import com.codingapi.springboot.security.gateway.Token; +import com.codingapi.springboot.security.gateway.TokenGateway; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -14,7 +16,7 @@ class TokenTest { @Autowired - private Jwt jwt; + private TokenGateway tokenGateway; @Test void verify1() throws TokenExpiredException { @@ -22,10 +24,10 @@ void verify1() throws TokenExpiredException { String iv = "123456"; List authorities = Collections.singletonList("ADMIN"); - Token token =jwt.create(username,iv,authorities); + Token token =tokenGateway.create(username,iv,authorities); token.verify(); - Token data = jwt.parser(token.getToken()); + Token data = tokenGateway.parser(token.getToken()); assertEquals(data.decodeIv(),iv); assertEquals(data.getAuthorities(),authorities); } @@ -35,10 +37,10 @@ void verify2() throws TokenExpiredException { String username = "admin"; List authorities = Collections.singletonList("ADMIN"); - Token token =jwt.create(username,authorities); + Token token =tokenGateway.create(username,authorities); token.verify(); - Token data = jwt.parser(token.getToken()); + Token data = tokenGateway.parser(token.getToken()); assertEquals(data.getUsername(),username); assertEquals(data.getAuthorities(),authorities); } @@ -52,10 +54,10 @@ void verify3() throws TokenExpiredException { String extra = testVO.toJson(); List authorities = Collections.singletonList("ADMIN"); - Token token =jwt.create(username,authorities,extra); + Token token =tokenGateway.create(username,authorities,extra); token.verify(); - Token data = jwt.parser(token.getToken()); + Token data = tokenGateway.parser(token.getToken()); assertEquals(data.parseExtra(TestVO.class).getName(), testVO.getName()); assertEquals(data.getAuthorities(),authorities); } diff --git a/springboot-starter-security-jwt/src/test/resources/application.properties b/springboot-starter-security/src/test/resources/application.properties similarity index 77% rename from springboot-starter-security-jwt/src/test/resources/application.properties rename to springboot-starter-security/src/test/resources/application.properties index 3848c355..d228eabb 100644 --- a/springboot-starter-security-jwt/src/test/resources/application.properties +++ b/springboot-starter-security/src/test/resources/application.properties @@ -1,14 +1,16 @@ server.port=8088 -codingapi.security.jwt-time=10000 -codingapi.security.jwt-rest-time=5000 +codingapi.security.jwt.valid-time=10000 +codingapi.security.jwt.rest-time=5000 + +codingapi.security.jwt.enable=true # JWT密钥 需大于32位的字符串 -codingapi.security.jwt-secret=codingapi.security.jwt.secretkey +codingapi.security.jwt.secret-key=codingapi.security.jwt.secretkey # JWT AES密钥 codingapi.security.ase-key=QUNEWCQlXiYqJCNYQ1phc0FDRFgkJV4mKiQjWENaYXM= # JWT AES IV -codingapi.security.aes-iv=QUNYRkdIQEVEUyNYQ1phcw== +codingapi.security.ase-iv=QUNYRkdIQEVEUyNYQ1phcw== # JWT 有效时间(毫秒) 15分钟有效期 1000*60*15=900000 #codingapi.security.jwt-time=900000 diff --git a/springboot-starter/pom.xml b/springboot-starter/pom.xml index 30709242..a314e5c3 100644 --- a/springboot-starter/pom.xml +++ b/springboot-starter/pom.xml @@ -5,14 +5,14 @@ com.codingapi.springboot springboot-parent - 2.1.11 + 2.10.5 springboot-starter springboot-starter springboot-starter project for Spring Boot - 17 + 8 @@ -21,6 +21,16 @@ spring-data-commons + + jakarta.persistence + jakarta.persistence-api + + + + com.fasterxml.jackson.core + jackson-databind + + org.springframework spring-tx @@ -38,7 +48,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on @@ -47,8 +57,8 @@ - org.apache.httpcomponents.client5 - httpclient5 + org.apache.httpcomponents + httpclient diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/ColumnType.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/ColumnType.java new file mode 100644 index 00000000..40bb84f1 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/ColumnType.java @@ -0,0 +1,52 @@ +package com.codingapi.springboot.framework.annotation; + +/** + * 数据库字段类型 + */ +public enum ColumnType { + + /** + * 整数 + */ + Number, + + /** + * 浮点数 + */ + Float, + + /** + * 字符串 + */ + String, + + /** + * 日期 + */ + Date, + + /** + * 文件 + */ + File, + + /** + * 布尔 + */ + Boolean, + + /** + * 字节 + */ + Bytes, + + /** + * JSON + */ + JSON, + + /** + * 任意 + */ + Any +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaColumn.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaColumn.java new file mode 100644 index 00000000..289a1d04 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaColumn.java @@ -0,0 +1,43 @@ +package com.codingapi.springboot.framework.annotation; + +import java.lang.annotation.*; + +/** + * 查询字段 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetaColumn { + + /** + * 字段说明 + */ + String desc(); + + /** + * 字段名称 + */ + String name(); + + /** + * 是否主键 + */ + boolean primaryKey() default false; + + /** + * 字段类型 + */ + ColumnType type() default ColumnType.String; + + /** + * 格式化 + */ + String format() default ""; + + /** + * 依赖表 + */ + MetaRelation dependent() default @MetaRelation(tableName = "", columnName = ""); + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaRelation.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaRelation.java new file mode 100644 index 00000000..2ee40710 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaRelation.java @@ -0,0 +1,20 @@ +package com.codingapi.springboot.framework.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetaRelation { + + /** + * 表名称 + */ + String tableName(); + + /** + * 字段名称 + */ + String columnName(); +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaTable.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaTable.java new file mode 100644 index 00000000..6075b1eb --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/annotation/MetaTable.java @@ -0,0 +1,23 @@ +package com.codingapi.springboot.framework.annotation; + +import java.lang.annotation.*; + +/** + * 查询表 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MetaTable { + + /** + * 表说明 + */ + String desc(); + + /** + * 表名称 + */ + String name(); + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/domain/ISort.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/domain/ISort.java new file mode 100644 index 00000000..f9efc10a --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/domain/ISort.java @@ -0,0 +1,8 @@ +package com.codingapi.springboot.framework.domain; + +public interface ISort { + + void setSort(Integer sort); + + Integer getSort(); +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Filter.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Filter.java new file mode 100644 index 00000000..c7223a90 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Filter.java @@ -0,0 +1,132 @@ +package com.codingapi.springboot.framework.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class Filter { + + public static final String FILTER_OR_KEY = "FILTER_OR_KEY"; + public static final String FILTER_AND_KEY = "FILTER_AND_KEY"; + + private String key; + private Object[] value; + private Relation relation; + + public Filter(String key, Relation relation, Object... value) { + this.key = key; + this.value = value; + this.relation = relation; + } + + public Filter(String key, Object... value) { + this(key, Relation.EQUAL, value); + } + + public Filter(String key, Filter... value) { + this(key, null, value); + } + + public static Filter as(String key, Relation relation, Object... value) { + return new Filter(key, relation, value); + } + + public static Filter as(String key, Object... value) { + return new Filter(key, value); + } + + public static Filter and(Filter... value) { + return new Filter(FILTER_AND_KEY, value); + } + + public static Filter or(Filter... value) { + return new Filter(FILTER_OR_KEY, value); + } + + public boolean isEqual() { + return relation == Relation.EQUAL; + } + + public boolean isNull() { + return relation == Relation.IS_NULL; + } + + public boolean isNotNull() { + return relation == Relation.IS_NOT_NULL; + } + + public boolean isNotIn() { + return relation == Relation.NOT_IN; + } + + public boolean isNotEqual() { + return relation == Relation.NOT_EQUAL; + } + + public boolean isLike() { + return relation == Relation.LIKE; + } + + + public boolean isLeftLike() { + return relation == Relation.LEFT_LIKE; + } + + public boolean isRightLike() { + return relation == Relation.RIGHT_LIKE; + } + + + public boolean isBetween() { + return relation == Relation.BETWEEN; + } + + public boolean isIn() { + return relation == Relation.IN; + } + + public boolean isOrFilters() { + return FILTER_OR_KEY.equals(key); + } + + public boolean isAndFilters() { + return FILTER_AND_KEY.equals(key); + } + + public boolean isGreaterThan() { + return relation == Relation.GREATER_THAN; + } + + public boolean isLessThan() { + return relation == Relation.LESS_THAN; + } + + public boolean isGreaterThanEqual() { + return relation == Relation.GREATER_THAN_EQUAL; + } + + public boolean isLessThanEqual() { + return relation == Relation.LESS_THAN_EQUAL; + } + + public Object getFilterValue(Class clazz) { + Object val = value[0]; + if (val instanceof String) { + if (clazz == Integer.class) { + return Integer.parseInt((String) val); + } + if (clazz == Long.class) { + return Long.parseLong((String) val); + } + if (clazz == Double.class) { + return Double.parseDouble((String) val); + } + if (clazz == Float.class) { + return Float.parseFloat((String) val); + } + } + return value[0]; + } + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/IdRequest.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/IdRequest.java new file mode 100644 index 00000000..b325423e --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/IdRequest.java @@ -0,0 +1,33 @@ +package com.codingapi.springboot.framework.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class IdRequest { + + private String id; + + public String getStringId(){ + return id; + } + + public int getIntId(){ + return Integer.parseInt(id); + } + + public Long getLongId(){ + return Long.parseLong(id); + } + + public float getFloatId(){ + return Float.parseFloat(id); + } + + public double getDoubleId(){ + return Double.parseDouble(id); + } + + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/PageRequest.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/PageRequest.java index b7af6da8..cf62e20e 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/PageRequest.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/PageRequest.java @@ -1,136 +1,64 @@ package com.codingapi.springboot.framework.dto.request; -import jakarta.servlet.http.HttpServletRequest; import lombok.Getter; -import org.springframework.beans.BeanUtils; -import org.springframework.data.domain.Example; -import org.springframework.data.domain.Pageable; +import lombok.Setter; import org.springframework.data.domain.Sort; -import org.springframework.util.StringUtils; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import java.beans.PropertyDescriptor; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; public class PageRequest extends org.springframework.data.domain.PageRequest { @Getter + @Setter private int current; + + @Setter private int pageSize; - private final Map filters = new HashMap<>(); + @Getter + private Sort sort; @Getter - private HttpServletRequest servletRequest; + private final RequestFilter requestFilter = new RequestFilter(); - private org.springframework.data.domain.PageRequest pageRequest; public PageRequest(int current, int pageSize, Sort sort) { - super(current > 0 ? current-- : 0, pageSize, sort); + super(current, pageSize, sort); this.current = current; this.pageSize = pageSize; - this.pageRequest = PageRequest.of(current, pageSize, sort); - - try { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); - this.servletRequest = attributes.getRequest(); - this.syncParameter(); - } catch (Exception e) { - } + this.sort = sort; } - private void syncParameter() { - Enumeration enumeration = servletRequest.getParameterNames(); - while (enumeration.hasMoreElements()) { - String key = enumeration.nextElement(); - String value = servletRequest.getParameter(key); - if (StringUtils.hasText(value)) { - this.filters.put(key, value); - } - } - } - public PageRequest() { this(0, 20, Sort.unsorted()); } - public void setCurrent(int current) { - this.current = current > 0 ? current - 1 : 0; - } - - public String getParameter(String key) { - return servletRequest.getParameter(key); - } - - public String getParameter(String key, String defaultValue) { - String result = servletRequest.getParameter(key); - return result == null ? defaultValue : result; - } - - public int getIntParameter(String key) { - return Integer.parseInt(servletRequest.getParameter(key)); - } - - public int getIntParameter(String key, int defaultValue) { - String result = servletRequest.getParameter(key); - return result == null ? defaultValue : Integer.parseInt(result); - } - - public Map getFilters() { - return filters; - } - public String getStringFilter(String key) { - return (String) filters.get(key); + return requestFilter.getStringFilter(key); } + public String getStringFilter(String key, String defaultValue) { - String result = (String) filters.get(key); - return result == null ? defaultValue : result; + return requestFilter.getStringFilter(key, defaultValue); } public int getIntFilter(String key) { - return Integer.parseInt((String) filters.get(key)); - } - - public int getIntFilter(String key, int defaultValue) { - String result = (String) filters.get(key); - return result == null ? defaultValue : Integer.parseInt(result); + return requestFilter.getIntFilter(key); } - @Override - public int getPageSize() { - return pageSize; + public int getIntFilter(String key, int defaultValue) { + return requestFilter.getIntFilter(key, defaultValue); } - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - @Override - public Sort getSort() { - return pageRequest.getSort(); - } - - @Override - public org.springframework.data.domain.PageRequest next() { - return new PageRequest(current + 1, getPageSize(), getSort()); + public boolean hasFilter() { + return requestFilter.hasFilter(); } - @Override - public org.springframework.data.domain.PageRequest previous() { - return current == 0 ? this : new PageRequest(current - 1, getPageSize(), getSort()); - } @Override - public org.springframework.data.domain.PageRequest first() { - return new PageRequest(0, getPageSize(), getSort()); + public int getPageSize() { + return pageSize; } @Override @@ -148,72 +76,44 @@ public boolean hasPrevious() { return current > 0; } - @Override - public Pageable previousOrFirst() { - return pageRequest.previousOrFirst(); - } - - @Override - public boolean isPaged() { - return pageRequest.isPaged(); + public void addSort(Sort sort) { + Sort nowSort = this.sort; + if (nowSort == Sort.unsorted()) { + this.sort = sort; + } else { + this.sort.and(sort); + } } - @Override - public boolean isUnpaged() { - return pageRequest.isUnpaged(); + public void removeFilter(String key) { + requestFilter.removeFilter(key); } - @Override - public Sort getSortOr(Sort sort) { - return pageRequest.getSortOr(sort); + public PageRequest addFilter(String key, Relation relation, Object... value) { + requestFilter.addFilter(key, relation, value); + return this; } - @Override - public Optional toOptional() { - return pageRequest.toOptional(); + public PageRequest addFilter(String key, Object... value) { + requestFilter.addFilter(key, value); + return this; } - public void addSort(Sort sort) { - Sort nowSort = pageRequest.getSort(); - if (nowSort == Sort.unsorted()) { - this.pageRequest = new PageRequest(getCurrent(), getPageSize(), sort); - } else { - pageRequest.getSort().and(sort); - } + public PageRequest andFilter(Filter... filters) { + requestFilter.andFilters(filters); + return this; } - public PageRequest addFilter(String key, Object value) { - this.filters.put(key, value); + public PageRequest orFilters(Filter... filters) { + requestFilter.orFilters(filters); return this; } - public boolean hasFilter() { - return !this.filters.isEmpty(); + public static PageRequest of(int page, int size) { + return new PageRequest(page, size, Sort.unsorted()); } - public Example getExample(Class clazz) { - if (!hasFilter()) { - return null; - } - Object entity = null; - try { - entity = clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz); - for (PropertyDescriptor descriptor : descriptors) { - String name = descriptor.getName(); - Object value = filters.get(name); - if (value != null) { - try { - descriptor.getWriteMethod().invoke(entity, value); - } catch (Exception e) { - } - } - } - return (Example) Example.of(entity); - + public static PageRequest of(int page, int size, Sort sort) { + return new PageRequest(page, size, sort); } } - diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Relation.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Relation.java new file mode 100644 index 00000000..3fd9fce5 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/Relation.java @@ -0,0 +1,19 @@ +package com.codingapi.springboot.framework.dto.request; + +public enum Relation { + + NOT_EQUAL, + EQUAL, + LIKE, + LEFT_LIKE, + RIGHT_LIKE, + BETWEEN, + IN, + NOT_IN, + IS_NULL, + IS_NOT_NULL, + GREATER_THAN, + LESS_THAN, + GREATER_THAN_EQUAL, + LESS_THAN_EQUAL, +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/RequestFilter.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/RequestFilter.java new file mode 100644 index 00000000..0773eac8 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/RequestFilter.java @@ -0,0 +1,96 @@ +package com.codingapi.springboot.framework.dto.request; + +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RequestFilter { + + private final Map filterMap = new HashMap<>(); + private final List filterList = new ArrayList<>(); + + public RequestFilter addFilter(String key, Relation relation, Object... value) { + this.pushFilter(new Filter(key, relation, value)); + return this; + } + + public RequestFilter addFilter(String key, Object... value) { + this.pushFilter(new Filter(key, value)); + return this; + } + + public RequestFilter andFilters(Filter... value) { + this.pushFilter(new Filter(Filter.FILTER_AND_KEY, value)); + return this; + } + + public RequestFilter orFilters(Filter... value) { + this.pushFilter(new Filter(Filter.FILTER_OR_KEY, value)); + return this; + } + + public List getFilters() { + return filterList; + } + + public void pushFilter(Filter filter) { + filterList.removeIf(item -> item.getKey().equals(filter.getKey())); + filterList.add(filter); + filterMap.put(filter.getKey(), filter); + } + + + public String getStringFilter(String key) { + Filter filter = filterMap.get(key); + if (filter != null) { + return (String) filter.getValue()[0]; + } + return null; + } + + public String getStringFilter(String key, String defaultValue) { + String value = getStringFilter(key); + if (!StringUtils.hasText(value)) { + return defaultValue; + } + return value; + } + + public int getIntFilter(String key) { + Filter filter = filterMap.get(key); + if (filter != null) { + String value = (String) filter.getValue()[0]; + if (StringUtils.hasText(value)) { + return Integer.parseInt(value); + } + return 0; + } + return 0; + } + + public int getIntFilter(String key, int defaultValue) { + int value = getIntFilter(key); + if (value == 0) { + return defaultValue; + } + return value; + } + + + public boolean hasFilter() { + return !this.filterMap.isEmpty(); + } + + + public Filter getFilter(String name) { + return this.filterMap.get(name); + } + + public void removeFilter(String key) { + this.filterMap.remove(key); + this.filterList.removeIf(item -> item.getKey().equals(key)); + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SearchRequest.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SearchRequest.java new file mode 100644 index 00000000..f14539ae --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SearchRequest.java @@ -0,0 +1,266 @@ +package com.codingapi.springboot.framework.dto.request; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.domain.Sort; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; + +/** + * HttpServletRequest 请求参数解析成 PageRequest对象 + */ +public class SearchRequest { + + @Getter + private int current; + @Getter + private int pageSize; + + private final HttpServletRequest request; + + private final List removeKeys = new ArrayList<>(); + + private final PageRequest pageRequest; + + public SearchRequest() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + this.request = attributes.getRequest(); + this.pageRequest = new PageRequest(); + } + + public void setCurrent(int current) { + this.current = current - 1; + this.removeKeys.add("current"); + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + this.removeKeys.add("pageSize"); + } + + public void addSort(Sort sort) { + pageRequest.addSort(sort); + } + + public void removeFilter(String key) { + pageRequest.removeFilter(key); + this.removeKeys.add(key); + } + + public String getParameter(String key) { + return request.getParameter(key); + } + + public String[] getParameterValues(String key) { + return request.getParameterValues(key); + } + + + public List getParameterNames() { + Enumeration enumeration = request.getParameterNames(); + List list = new ArrayList<>(); + while (enumeration.hasMoreElements()) { + list.add(enumeration.nextElement()); + } + return list; + } + + public PageRequest addFilter(String key, Relation relation, Object... value) { + return pageRequest.addFilter(key, relation, value); + } + + public PageRequest addFilter(String key, Object... value) { + return pageRequest.addFilter(key, value); + } + + public PageRequest andFilter(Filter... filters) { + return pageRequest.andFilter(filters); + } + + public PageRequest orFilters(Filter... filters) { + return pageRequest.orFilters(filters); + } + + + private String decode(String value) { + return new String(Base64.getDecoder().decode(value)); + } + + + static class ClassContent { + + private final Class clazz; + private final PageRequest pageRequest; + + public ClassContent(Class clazz, PageRequest pageRequest) { + this.clazz = clazz; + this.pageRequest = pageRequest; + } + + public void addFilter(String key, Relation relation, String value) { + Class keyClass = getKeyType(key); + Object v = parseObject(value, keyClass); + pageRequest.addFilter(key, relation, v); + } + + public void addFilter(String key, String value) { + Class keyClass = getKeyType(key); + Object v = parseObject(value, keyClass); + pageRequest.addFilter(key, Relation.EQUAL, v); + } + + private Object parseObject(String value, Class keyClass) { + if (value.getClass().equals(keyClass)) { + return value; + } + return JSON.parseObject(value, keyClass); + } + + public void addFilter(String key, List value) { + Class keyClass = getKeyType(key); + pageRequest.addFilter(key, Relation.IN, value.stream() + .map(v -> parseObject(v, keyClass)) + .toArray() + ); + } + + + + private Class getKeyType(String key) { + String[] keys = key.split("\\."); + Class keyClass = clazz; + + for (String k : keys) { + keyClass = findFieldInHierarchy(keyClass, k); + + if (keyClass == null) { + throw new IllegalArgumentException("Field " + k + " not found in class hierarchy."); + } + } + return keyClass; + } + + private Class findFieldInHierarchy(Class clazz, String fieldName) { + Class currentClass = clazz; + + while (currentClass != null) { + Field[] fields = currentClass.getDeclaredFields(); + for (Field field : fields) { + if (field.getName().equals(fieldName)) { + return field.getType(); + } + } + currentClass = currentClass.getSuperclass(); // 向上查找父类 + } + return null; + } + + } + + @Setter + @Getter + static class ParamOperation { + private String key; + private String type; + + public Relation getOperation() { + return Relation.valueOf(type); + } + } + + private List loadParamOperations() { + String params = request.getParameter("params"); + if (StringUtils.hasLength(params)) { + params = decode(params); + if (JSON.isValid(params)) { + removeKeys.add("params"); + return JSON.parseArray(params, ParamOperation.class); + } + } + return null; + } + + public PageRequest toPageRequest(Class clazz) { + pageRequest.setCurrent(current); + pageRequest.setPageSize(pageSize); + + ClassContent content = new ClassContent(clazz, pageRequest); + + List loadParams = loadParamOperations(); + + String sort = request.getParameter("sort"); + if (StringUtils.hasLength(sort)) { + sort = decode(sort); + if (JSON.isValid(sort)) { + removeKeys.add("sort"); + JSONObject jsonObject = JSON.parseObject(sort); + for (String key : jsonObject.keySet()) { + String value = jsonObject.getString(key); + if ("ascend".equals(value)) { + pageRequest.addSort(Sort.by(key).ascending()); + } else { + pageRequest.addSort(Sort.by(key).descending()); + } + } + } + } + + + String filter = request.getParameter("filter"); + if (StringUtils.hasLength(filter)) { + filter = decode(filter); + if (JSON.isValid(filter)) { + removeKeys.add("filter"); + JSONObject jsonObject = JSON.parseObject(filter); + if(jsonObject!=null) { + for (String key : jsonObject.keySet()) { + JSONArray value = jsonObject.getJSONArray(key); + if (value != null && !value.isEmpty()) { + List values = value.stream().map(Object::toString).collect(Collectors.toList()); + content.addFilter(key, values); + } + } + } + } + } + + Enumeration enumeration = request.getParameterNames(); + + while (enumeration.hasMoreElements()){ + String key = enumeration.nextElement(); + if (!removeKeys.contains(key)) { + String value = request.getParameter(key); + if (StringUtils.hasLength(value)) { + if (loadParams != null) { + ParamOperation operation = loadParams.stream() + .filter(paramOperation -> paramOperation.getKey().equals(key)) + .findFirst() + .orElse(null); + if (operation != null) { + content.addFilter(key, operation.getOperation(), value); + } else { + content.addFilter(key, value); + } + } else { + content.addFilter(key, value); + } + } + } + } + return pageRequest; + } + + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SortRequest.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SortRequest.java new file mode 100644 index 00000000..25d5a9b9 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/dto/request/SortRequest.java @@ -0,0 +1,14 @@ +package com.codingapi.springboot.framework.dto.request; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Setter +@Getter +public class SortRequest { + + private List ids; + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/em/IEnum.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/em/IEnum.java new file mode 100644 index 00000000..d24151b1 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/em/IEnum.java @@ -0,0 +1,6 @@ +package com.codingapi.springboot.framework.em; + +public interface IEnum { + + int getCode(); +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/ApplicationHandlerUtils.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/ApplicationHandlerUtils.java new file mode 100644 index 00000000..61c7a932 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/ApplicationHandlerUtils.java @@ -0,0 +1,96 @@ +package com.codingapi.springboot.framework.event; + +import com.codingapi.springboot.framework.exception.EventException; +import com.codingapi.springboot.framework.exception.EventLoopException; +import org.springframework.core.ResolvableType; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +class ApplicationHandlerUtils implements IHandler { + + private static ApplicationHandlerUtils instance; + private final List> handlers; + + + private ApplicationHandlerUtils() { + this.handlers = new ArrayList<>(); + } + + public static ApplicationHandlerUtils getInstance() { + if (instance == null) { + synchronized (ApplicationHandlerUtils.class) { + if (instance == null) { + instance = new ApplicationHandlerUtils(); + } + } + } + return instance; + } + + public void addHandlers(List handlers) { + if (handlers != null) { + handlers.forEach(this::addHandler); + } + } + + + public void addHandler(IHandler handler) { + if (handler != null) { + handlers.add(handler); + } + } + + /** + * 获取订阅的事件类型 + */ + private Class getHandlerEventClass(IHandler handler) { + ResolvableType resolvableType = ResolvableType.forClass(handler.getClass()).as(IHandler.class); + return resolvableType.getGeneric(0).resolve(); + } + + + @Override + public void handler(IEvent event) { + Class eventClass = event.getClass(); + + List> matchHandlers = handlers + .stream() + .filter(handler -> { + Class targetClass = getHandlerEventClass(handler); + return targetClass.isAssignableFrom(eventClass); + }) + .sorted(Comparator.comparingInt(IHandler::order)) + .collect(Collectors.toList()); + + if (matchHandlers.isEmpty()) { + return; + } + + List errorStack = new ArrayList<>(); + boolean hasThrowException = false; + for (IHandler handler : matchHandlers) { + try { + handler.handler(event); + } catch (Exception e) { + if (e instanceof EventLoopException) { + throw e; + } + try { + handler.error(e); + errorStack.add(e); + } catch (Exception err) { + hasThrowException = true; + errorStack.add(err); + } + } + } + if (hasThrowException) { + throw new EventException(errorStack); + } + } + + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEvent.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEvent.java index 1f478ac1..a03d694e 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEvent.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEvent.java @@ -18,10 +18,14 @@ public class DomainEvent extends ApplicationEvent { @Getter private final boolean sync; - public DomainEvent(Object source, boolean sync) { + @Getter + private final String traceId; + + + public DomainEvent(Object source, boolean sync, String traceId) { super(source); this.event = (IEvent) source; this.sync = sync; + this.traceId = traceId; } - } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEventContext.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEventContext.java index f7bcd7c2..7d10e38c 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEventContext.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/DomainEventContext.java @@ -23,9 +23,15 @@ public static DomainEventContext getInstance() { return instance; } - private void push(IEvent event, boolean sync) { + private void push(IEvent event, boolean sync,boolean hasLoopEvent) { if (context != null) { - context.publishEvent(new DomainEvent(event, sync)); + String traceId = EventTraceContext.getInstance().getOrCreateTrace(); + if(hasLoopEvent){ + EventTraceContext.getInstance().clearTrace(); + traceId = EventTraceContext.getInstance().getOrCreateTrace(); + } + EventTraceContext.getInstance().addEvent(traceId,event); + context.publishEvent(new DomainEvent(event, sync,traceId)); } } @@ -34,13 +40,13 @@ private void push(IEvent event, boolean sync) { * @see EventPusher * 默认 同步事件 */ - public void push(IEvent event) { + public void push(IEvent event,boolean hasLoopEvent) { if (event instanceof IAsyncEvent) { - this.push(event, false); + this.push(event, false,hasLoopEvent); } else if (event instanceof ISyncEvent) { - this.push(event, true); + this.push(event, true,hasLoopEvent); } else { - this.push(event, true); + this.push(event, true,hasLoopEvent); } } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventPusher.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventPusher.java index ee90ddd8..333569ea 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventPusher.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventPusher.java @@ -5,7 +5,23 @@ */ public class EventPusher { + /** + * 推送事件 + * 默认将自动检测事件是否有循环事件,当出现循环事件时,系统将会抛出循环调用异常。 + * @param event 事件 + */ public static void push(IEvent event) { - DomainEventContext.getInstance().push(event); + push(event, false); + } + + /** + * 推送事件 + * 默认将自动检测事件是否有循环事件,当出现循环事件时,系统将会抛出循环调用异常。 + * 设置hasLoopEvent为true,将不会检测循环事件。 + * @param event 事件 + * @param hasLoopEvent 是否有循环事件 + */ + public static void push(IEvent event, boolean hasLoopEvent) { + DomainEventContext.getInstance().push(event, hasLoopEvent); } } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventStackContext.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventStackContext.java new file mode 100644 index 00000000..bcc01407 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventStackContext.java @@ -0,0 +1,80 @@ +package com.codingapi.springboot.framework.event; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 事件栈上下文 + */ +public class EventStackContext { + + private final Map>> eventClassStack = new HashMap<>(); + private final Map> eventStack = new HashMap<>(); + + @Getter + private final static EventStackContext instance = new EventStackContext(); + + private EventStackContext() { + + } + + private void addEventClass(String traceId, IEvent event) { + List> events = eventClassStack.get(traceId); + if (events == null) { + events = new ArrayList<>(); + } + events.add(event.getClass()); + eventClassStack.put(traceId, events); + } + + private void addEventStack(String traceId, IEvent event) { + List events = eventStack.get(traceId); + if (events == null) { + events = new ArrayList<>(); + } + events.add(event); + eventStack.put(traceId, events); + } + + + void addEvent(String traceId, IEvent event) { + addEventClass(traceId, event); + addEventStack(traceId, event); + } + + boolean checkEventLoop(String traceId, IEvent event) { + List> events = eventClassStack.get(traceId); + if (events != null) { + return events.contains(event.getClass()); + } + return false; + } + + public List getEvents(String eventKey) { + if(eventKey!=null) { + String traceId = eventKey.split("#")[0]; + return eventStack.get(traceId); + } + return null; + } + + public List> getEventClasses(String eventKey) { + if(eventKey!=null) { + String traceId = eventKey.split("#")[0]; + return eventClassStack.get(traceId); + } + return null; + } + + + void remove(String traceId) { + eventStack.remove(traceId); + eventClassStack.remove(traceId); + } + + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventTraceContext.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventTraceContext.java new file mode 100644 index 00000000..562e2768 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/EventTraceContext.java @@ -0,0 +1,107 @@ +package com.codingapi.springboot.framework.event; + +import com.codingapi.springboot.framework.exception.EventLoopException; +import com.codingapi.springboot.framework.utils.RandomGenerator; +import lombok.Getter; + +import java.util.*; + +/** + * 事件跟踪上下文 + */ +public class EventTraceContext { + + @Getter + private final static EventTraceContext instance = new EventTraceContext(); + + // trace key + private final Set traceKeys = new HashSet<>(); + + // thread local + private final ThreadLocal threadLocal = new ThreadLocal<>(); + + // event listenerKey state + private final Map eventKeyState = new HashMap<>(); + + + private EventTraceContext() { + } + + String getOrCreateTrace() { + String eventKey = threadLocal.get(); + if (eventKey != null) { + return eventKey.split("#")[0]; + } + String traceId = UUID.randomUUID().toString().replaceAll("-", ""); + traceKeys.add(traceId); + return traceId; + } + + /** + * get event key + * traceId = eventKey.split("#")[0] + */ + public String getEventKey() { + return threadLocal.get(); + } + + /** + * create event key + * @param traceId traceId + */ + void createEventKey(String traceId) { + String eventKey = traceId + "#" + RandomGenerator.randomString(8); + eventKeyState.put(eventKey, false); + threadLocal.set(eventKey); + } + + /** + * check event state + */ + void checkEventState() { + String eventKey = threadLocal.get(); + if (eventKey != null) { + boolean state = eventKeyState.get(eventKey); + if (!state) { + // event execute finish + String traceId = eventKey.split("#")[0]; + traceKeys.remove(traceId); + EventStackContext.getInstance().remove(traceId); + } + eventKeyState.remove(eventKey); + } + threadLocal.remove(); + } + + /** + * add event + * @param traceId traceId + * @param event event + */ + void addEvent(String traceId, IEvent event) { + boolean hasEventLoop = EventStackContext.getInstance().checkEventLoop(traceId, event); + if (hasEventLoop) { + List> stack = EventStackContext.getInstance().getEventClasses(traceId); + traceKeys.remove(traceId); + EventStackContext.getInstance().remove(traceId); + eventKeyState.remove(traceId); + threadLocal.remove(); + throw new EventLoopException(stack, event); + } + EventStackContext.getInstance().addEvent(traceId, event); + } + + /** + * clear trace + */ + public void clearTrace() { + String eventKey = threadLocal.get(); + if (eventKey != null) { + String traceId = eventKey.split("#")[0]; + traceKeys.remove(traceId); + EventStackContext.getInstance().remove(traceId); + eventKeyState.remove(eventKey); + threadLocal.remove(); + } + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java similarity index 76% rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java index d4321a03..6dda24a0 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java @@ -1,4 +1,4 @@ -package com.codingapi.springboot.framework.handler; +package com.codingapi.springboot.framework.event; import java.lang.annotation.*; diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java similarity index 96% rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java index 3f1a76b3..de878c8b 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java @@ -1,4 +1,4 @@ -package com.codingapi.springboot.framework.handler; +package com.codingapi.springboot.framework.event; import com.codingapi.springboot.framework.registrar.RegisterBeanScanner; import lombok.SneakyThrows; diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java index ca03ea12..4cc12388 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java @@ -1,6 +1,8 @@ package com.codingapi.springboot.framework.event; +import java.io.Serializable; + /** * 默认同步事件 *

@@ -8,7 +10,6 @@ * 事件本身不应该同步主业务的事务,即事件对于主业务来说,可成功可失败,成功与失败都不应该强关联主体业务。 * 若需要让主体业务与分支做事务同步的时候,那不应该采用事件机制,而应该直接采用调用的方式实现业务绑定。 */ -public interface IEvent { - +public interface IEvent extends Serializable { } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java similarity index 51% rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java index 45db5dd7..46248d8c 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java @@ -1,6 +1,4 @@ -package com.codingapi.springboot.framework.handler; - -import com.codingapi.springboot.framework.event.IEvent; +package com.codingapi.springboot.framework.event; /** * handler 订阅 @@ -9,6 +7,14 @@ */ public interface IHandler { + /** + * 事件订阅排序 + * 在同样的事件中,可以通过order来控制订阅的顺序 + */ + default int order() { + return 0; + } + /** * 订阅触发 * @@ -18,12 +24,15 @@ public interface IHandler { /** * 异常回掉,在多订阅的情况下,为了实现订阅的独立性,将异常的处理放在回掉函数中。 + * 当异常抛出以后,会阻止后续的事件执行 * * @param exception 异常信息 */ - default void error(Exception exception) { + default void error(Exception exception) throws Exception { + throw exception; } - ; + + } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java similarity index 64% rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java index 9796511b..5ec7b9ba 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java @@ -1,6 +1,5 @@ -package com.codingapi.springboot.framework.handler; +package com.codingapi.springboot.framework.event; -import com.codingapi.springboot.framework.event.DomainEvent; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; @@ -28,11 +27,23 @@ public SpringEventHandler(List handlers) { @Override public void onApplicationEvent(DomainEvent domainEvent) { + String traceId = domainEvent.getTraceId(); + if (domainEvent.isSync()) { - ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent()); + try { + EventTraceContext.getInstance().createEventKey(traceId); + ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent()); + } finally { + EventTraceContext.getInstance().checkEventState(); + } } else { executorService.execute(() -> { - ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent()); + try { + EventTraceContext.getInstance().createEventKey(traceId); + ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent()); + } finally { + EventTraceContext.getInstance().checkEventState(); + } }); } } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java similarity index 89% rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java index 0f5c989c..e3afeb12 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java @@ -1,4 +1,4 @@ -package com.codingapi.springboot.framework.handler; +package com.codingapi.springboot.framework.event; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java new file mode 100644 index 00000000..5a506fec --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java @@ -0,0 +1,20 @@ +package com.codingapi.springboot.framework.exception; + +import lombok.Getter; + +import java.util.List; +import java.util.stream.Collectors; + +@Getter +public class EventException extends RuntimeException { + + private final List error; + + public EventException(List error) { + super(error.stream().map(Exception::getMessage).collect(Collectors.joining("\n"))); + this.error = error; + for (Exception e : error) { + e.printStackTrace(); + } + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java new file mode 100644 index 00000000..cb17f046 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java @@ -0,0 +1,17 @@ +package com.codingapi.springboot.framework.exception; + +import com.codingapi.springboot.framework.event.IEvent; +import lombok.Getter; + +import java.util.List; + +@Getter +public class EventLoopException extends RuntimeException { + + private final List> stack; + + public EventLoopException(List> stack, IEvent event) { + super("event loop error current event class:" + event.getClass() + ", history event stack:" + stack); + this.stack = stack; + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/ApplicationHandlerUtils.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/ApplicationHandlerUtils.java deleted file mode 100644 index 7817ae56..00000000 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/ApplicationHandlerUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.codingapi.springboot.framework.handler; - -import com.codingapi.springboot.framework.event.IEvent; -import lombok.extern.slf4j.Slf4j; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -@Slf4j -class ApplicationHandlerUtils implements IHandler { - - private static ApplicationHandlerUtils instance; - private List> handlers; - - - private ApplicationHandlerUtils() { - this.handlers = new ArrayList<>(); - } - - public static ApplicationHandlerUtils getInstance() { - if (instance == null) { - synchronized (ApplicationHandlerUtils.class) { - if (instance == null) { - instance = new ApplicationHandlerUtils(); - } - } - } - return instance; - } - - public void addHandlers(List handlers) { - if (handlers != null) { - handlers.forEach(this::addHandler); - } - } - - - public void addHandler(IHandler handler) { - if (handler != null) { - handlers.add(handler); - } - } - - - @Override - public void handler(IEvent event) { - for (IHandler handler : handlers) { - String targetClassName = null; - try { - Class eventClass = event.getClass(); - Class targetClass = getHandlerEventClass(handler); - if (eventClass.equals(targetClass)) { - targetClassName = targetClass.getName(); - handler.handler(event); - } - } catch (Exception e) { - //IPersistenceEvent 抛出异常 - if ("com.codingapi.springboot.framework.persistence.PersistenceEvent".equals(targetClassName)) { - throw e; - } - log.warn("handler exception", e); - handler.error(e); - - } - } - } - - private Class getHandlerEventClass(IHandler handler) { - Type[] types = handler.getClass().getGenericInterfaces(); - for (Type type : types) { - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments != null) { - return (Class) actualTypeArguments[0]; - } - } - } - return null; - } - - -} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/rest/param/RestParam.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/rest/param/RestParam.java index 51c08a8b..465d0b0f 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/rest/param/RestParam.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/rest/param/RestParam.java @@ -1,12 +1,11 @@ package com.codingapi.springboot.framework.rest.param; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import lombok.SneakyThrows; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; public class RestParam { @@ -35,19 +34,6 @@ private static void fetch(JSONObject object, RestParam builder) { Object value = object.getObject(key, Object.class); if (value != null) { builder.add(key, value); - - if (value instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) value; - fetch(jsonObject, builder); - } - - if (value instanceof JSONArray jsonArray) { - for (Object o : jsonArray) { - if (o instanceof JSONObject jsonObject) { - fetch(jsonObject, builder); - } - } - } } } } @@ -64,9 +50,10 @@ public RestParam add(String key, Object value) { return add(key, value, true); } + @SneakyThrows public RestParam add(String key, Object value, boolean encode) { String stringValue = value.toString(); - String encodeValue = encode ? URLEncoder.encode(stringValue, StandardCharsets.UTF_8) : value.toString(); + String encodeValue = encode ? URLEncoder.encode(stringValue, "UTF-8") : value.toString(); jsonBody.put(key, value); mapBody.add(key, encodeValue); return this; diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/EnumSerializer.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/EnumSerializer.java new file mode 100644 index 00000000..338dcf20 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/EnumSerializer.java @@ -0,0 +1,16 @@ +package com.codingapi.springboot.framework.serializable; + +import com.codingapi.springboot.framework.em.IEnum; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +public class EnumSerializer extends JsonSerializer { + + @Override + public void serialize(IEnum iEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNumber(iEnum.getCode()); + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/MapSerializable.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/MapSerializable.java index e7f96743..2b40aae7 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/MapSerializable.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/serializable/MapSerializable.java @@ -1,6 +1,7 @@ package com.codingapi.springboot.framework.serializable; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; import java.util.Map; @@ -11,6 +12,6 @@ public interface MapSerializable { default Map toMap() { - return (Map) JSONObject.toJSON(this); + return JSON.parseObject(JSONObject.toJSONString(this)); } } diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/servlet/BasicHandlerExceptionResolverConfiguration.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/servlet/BasicHandlerExceptionResolverConfiguration.java index 573de914..8cf34dc3 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/servlet/BasicHandlerExceptionResolverConfiguration.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/servlet/BasicHandlerExceptionResolverConfiguration.java @@ -2,8 +2,6 @@ import com.codingapi.springboot.framework.exception.LocaleMessageException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -12,6 +10,9 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + @Configuration @ConditionalOnClass(name = "org.springframework.web.servlet.HandlerExceptionResolver") public class BasicHandlerExceptionResolverConfiguration { diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/ClassLoaderUtils.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/ClassLoaderUtils.java new file mode 100644 index 00000000..df9cd18b --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/ClassLoaderUtils.java @@ -0,0 +1,99 @@ +package com.codingapi.springboot.framework.utils; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ClassLoaderUtils { + + public static URLClassLoader createClassLoader(String jarPath) throws MalformedURLException { + File file = new File(jarPath); + if(!file.exists()){ + throw new RuntimeException("jar file not found:"+jarPath); + } + URL[] urls = new URL[]{file.toURI().toURL()}; + return new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); + } + + public static List findAllClasses(URLClassLoader classLoader) throws URISyntaxException, IOException { + List classNames = new ArrayList<>(); + URL[] urls = classLoader.getURLs(); + for (URL url : urls) { + if (url.getProtocol().equals("file")) { + File file = new File(url.toURI()); + if (file.isDirectory()) { + classNames.addAll(findClassesInDirectory(file, "")); + } else if (file.getName().endsWith(".jar")) { + classNames.addAll(findClassesInJar(new JarFile(file))); + } + } + } + return classNames; + } + + public static List> findJarClasses(String jarPath) throws IOException, URISyntaxException { + URLClassLoader classLoader = createClassLoader(jarPath); + List classList = ClassLoaderUtils.findAllClasses(classLoader); + List> classes = new ArrayList<>(); + for(String className:classList){ + try { + Class driverClass = classLoader.loadClass(className); + classes.add(driverClass); + }catch (NoClassDefFoundError | ClassNotFoundException ignored){} + } + return classes; + } + + public static List> findJarClass(String jarPath,Class clazz) throws IOException, URISyntaxException { + URLClassLoader classLoader = createClassLoader(jarPath); + List classList = ClassLoaderUtils.findAllClasses(classLoader); + List> classes = new ArrayList<>(); + for(String className:classList){ + try { + Class driverClass = classLoader.loadClass(className); + if(clazz.isAssignableFrom(driverClass)){ + classes.add(driverClass); + } + }catch (NoClassDefFoundError | ClassNotFoundException ignored){} + } + return classes; + } + + private static List findClassesInDirectory(File directory, String packageName) { + List classNames = new ArrayList<>(); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + classNames.addAll(findClassesInDirectory(file, packageName + file.getName() + ".")); + } else if (file.getName().endsWith(".class")) { + String className = packageName + file.getName().replace(".class", ""); + classNames.add(className); + } + } + } + return classNames; + } + + private static List findClassesInJar(JarFile jarFile) { + List classNames = new ArrayList<>(); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + String className = name.replace('/', '.').replace(".class", ""); + classNames.add(className); + } + } + return classNames; + } +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java new file mode 100644 index 00000000..d81723c6 --- /dev/null +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java @@ -0,0 +1,22 @@ +package com.codingapi.springboot.framework.utils; + +import java.util.UUID; + +public class RandomGenerator { + + public static String generateUUID() { + return UUID.randomUUID().toString(); + } + + + public static String randomString(int length) { + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + int number = (int) (Math.random() * str.length()); + sb.append(str.charAt(number)); + } + return sb.toString(); + } + +} diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/TrustAnyHttpClientFactory.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/TrustAnyHttpClientFactory.java index 25810885..2bc34c3d 100644 --- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/TrustAnyHttpClientFactory.java +++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/TrustAnyHttpClientFactory.java @@ -2,16 +2,17 @@ import lombok.SneakyThrows; -import org.apache.hc.client5.http.classic.HttpClient; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.config.Registry; -import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; + import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; diff --git a/springboot-starter/src/main/resources/META-INF/banner.txt b/springboot-starter/src/main/resources/META-INF/banner.txt new file mode 100644 index 00000000..9555bc0b --- /dev/null +++ b/springboot-starter/src/main/resources/META-INF/banner.txt @@ -0,0 +1,4 @@ +------------------------------------------------------ +CodingApi SpringBoot-Starter 2.10.5 +springboot version (${spring-boot.version}) +------------------------------------------------------ diff --git a/springboot-starter/src/main/resources/META-INF/spring.factories b/springboot-starter/src/main/resources/META-INF/spring.factories index 5765e102..3fd6c549 100644 --- a/springboot-starter/src/main/resources/META-INF/spring.factories +++ b/springboot-starter/src/main/resources/META-INF/spring.factories @@ -2,6 +2,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.codingapi.springboot.framework.AutoConfiguration,\ com.codingapi.springboot.framework.event.SpringEventConfiguration,\ com.codingapi.springboot.framework.exception.ExceptionConfiguration,\ -com.codingapi.springboot.framework.handler.HandlerBeanDefinitionRegistrar,\ -com.codingapi.springboot.framework.handler.SpringHandlerConfiguration,\ -com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration \ No newline at end of file +com.codingapi.springboot.framework.event.HandlerBeanDefinitionRegistrar,\ +com.codingapi.springboot.framework.event.SpringHandlerConfiguration,\ +com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration diff --git a/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 39319e71..8aa6f4cd 100644 --- a/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,6 +1,6 @@ com.codingapi.springboot.framework.AutoConfiguration com.codingapi.springboot.framework.event.SpringEventConfiguration com.codingapi.springboot.framework.exception.ExceptionConfiguration -com.codingapi.springboot.framework.handler.HandlerBeanDefinitionRegistrar -com.codingapi.springboot.framework.handler.SpringHandlerConfiguration -com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration \ No newline at end of file +com.codingapi.springboot.framework.event.HandlerBeanDefinitionRegistrar +com.codingapi.springboot.framework.event.SpringHandlerConfiguration +com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/dto/request/PageRequestTest.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/dto/request/PageRequestTest.java index e3584bfa..93028dd8 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/dto/request/PageRequestTest.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/dto/request/PageRequestTest.java @@ -9,7 +9,7 @@ class PageRequestTest { @Test void test(){ PageRequest pageRequest = new PageRequest(); - pageRequest.setCurrent(2); + pageRequest.setCurrent(1); pageRequest.setPageSize(10); assertEquals(pageRequest.getCurrent(),1); diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java index 0f2d3bb1..f1efb83b 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java @@ -1,6 +1,8 @@ package com.codingapi.springboot.framework.handler; import com.codingapi.springboot.framework.event.DemoChangeEvent; +import com.codingapi.springboot.framework.event.Handler; +import com.codingapi.springboot.framework.event.IHandler; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java index f48c9fcc..01ab9aa5 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java @@ -1,6 +1,8 @@ package com.codingapi.springboot.framework.handler; import com.codingapi.springboot.framework.domain.event.DomainCreateEvent; +import com.codingapi.springboot.framework.event.Handler; +import com.codingapi.springboot.framework.event.IHandler; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java index 89885b16..6f02be80 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java @@ -1,6 +1,8 @@ package com.codingapi.springboot.framework.handler; import com.codingapi.springboot.framework.domain.event.DomainDeleteEvent; +import com.codingapi.springboot.framework.event.Handler; +import com.codingapi.springboot.framework.event.IHandler; import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java index cb3dae62..2d18934f 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java @@ -1,11 +1,13 @@ package com.codingapi.springboot.framework.handler; import com.codingapi.springboot.framework.domain.event.DomainPersistEvent; +import com.codingapi.springboot.framework.event.Handler; +import com.codingapi.springboot.framework.event.IHandler; import lombok.extern.slf4j.Slf4j; @Slf4j @Handler -public class DemoPersistEventHandler implements IHandler{ +public class DemoPersistEventHandler implements IHandler { @Override public void handler(DomainPersistEvent event) { diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java index b6bf661f..d4fb12b0 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java @@ -1,11 +1,13 @@ package com.codingapi.springboot.framework.handler; import com.codingapi.springboot.framework.domain.event.DomainChangeEvent; +import com.codingapi.springboot.framework.event.Handler; +import com.codingapi.springboot.framework.event.IHandler; import lombok.extern.slf4j.Slf4j; @Slf4j @Handler -public class EntityFiledChangeHandler implements IHandler{ +public class EntityFiledChangeHandler implements IHandler { @Override public void handler(DomainChangeEvent event) { diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/entity/Demo.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/entity/Demo.java index 664f815a..148a34ae 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/entity/Demo.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/entity/Demo.java @@ -1,9 +1,10 @@ package com.codingapi.springboot.framework.query.entity; -import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import javax.persistence.*; + @Setter @Getter @Table(name = "t_demo") diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/test/DemoRepositoryTest.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/test/DemoRepositoryTest.java index 3ae7c3e2..2b787c96 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/test/DemoRepositoryTest.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/query/test/DemoRepositoryTest.java @@ -46,9 +46,9 @@ void query(){ request.setCurrent(1); request.setPageSize(10); - request.addFilter("name","123"); - - Example demo = request.getExample(Demo.class); + Demo search = new Demo(); + search.setName("123"); + Example demo = Example.of(search); System.out.println(demo); Page page = demoRepository.findAll(demo,request); assertEquals(1, page.getTotalElements()); @@ -67,7 +67,7 @@ void sort(){ demoRepository.save(demo2); PageRequest request = new PageRequest(); - request.setCurrent(1); + request.setCurrent(0); request.setPageSize(10); request.addSort(Sort.by("id").descending()); diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/rest/RestClientTest.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/rest/RestClientTest.java index c6a48d79..94a3d3ff 100644 --- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/rest/RestClientTest.java +++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/rest/RestClientTest.java @@ -5,6 +5,7 @@ import com.codingapi.springboot.framework.rest.properties.HttpProxyProperties; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; import java.net.Proxy; @@ -21,14 +22,21 @@ void okxTest() { proxyProperties.setProxyType(Proxy.Type.HTTP); proxyProperties.setProxyHost("127.0.0.1"); proxyProperties.setProxyPort(7890); + + HttpHeaders headers = new HttpHeaders(); + headers.set("x-simulated-trading","1"); + headers.set("User-Agent", "Application"); RestClient restClient = new RestClient(proxyProperties,baseUrl,5,"{}",null,null); - String response = restClient.get("api/v5/market/candles", RestParam.create() - .add("instId","BTC-USDT") - .add("bar","1m") - .add("limit","300") + String response = restClient.get( + "api/v5/market/candles", + headers, + RestParam.create() + .add("instId","BTC-USDT") + .add("bar","1m") + .add("limit","300") ); log.info("response:{}",response); JSONObject jsonObject = JSONObject.parseObject(response); assertEquals(jsonObject.getJSONArray("data").size(),300); } -} \ No newline at end of file +} diff --git a/springboot-starter/src/test/resources/application.properties b/springboot-starter/src/test/resources/application.properties index c3ec648b..559cfc80 100644 --- a/springboot-starter/src/test/resources/application.properties +++ b/springboot-starter/src/test/resources/application.properties @@ -1,5 +1,6 @@ server.port=8088 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:file:./test.db +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true \ No newline at end of file