Commit f16598b43691b7b1ffc9d294bf7ffaefaafac946
0 parents
boss project init
Showing
90 changed files
with
4498 additions
and
0 deletions
.gitignore
0 → 100644
1 | +++ a/.gitignore | |
1 | +### Java template | |
2 | +# Compiled class file | |
3 | +*.class | |
4 | + | |
5 | +# Log file | |
6 | +*.log | |
7 | + | |
8 | +# BlueJ files | |
9 | +*.ctxt | |
10 | + | |
11 | +# Mobile Tools for Java (J2ME) | |
12 | +.mtj.tmp/ | |
13 | + | |
14 | +# Package Files # | |
15 | +*.jar | |
16 | +*.war | |
17 | +*.nar | |
18 | +*.ear | |
19 | +*.zip | |
20 | +*.tar.gz | |
21 | +*.rar | |
22 | + | |
23 | +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | |
24 | +hs_err_pid* | |
25 | +replay_pid* | |
26 | + | |
27 | +.idea/ | |
28 | +*/target/ | |
29 | +logs/ | |
30 | +*.iml | |
31 | + | |
32 | + | |
33 | +### Gradle template | |
34 | +.gradle | |
35 | +**/build/ | |
36 | +!src/**/build/ | |
37 | + | |
38 | +# Ignore Gradle GUI config | |
39 | +gradle-app.setting | |
40 | + | |
41 | +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) | |
42 | +!gradle-wrapper.jar | |
43 | + | |
44 | +# Avoid ignore Gradle wrappper properties | |
45 | +!gradle-wrapper.properties | |
46 | + | |
47 | +# Cache of project | |
48 | +.gradletasknamecache | |
49 | + | |
50 | +# Eclipse Gradle plugin generated files | |
51 | +# Eclipse Core | |
52 | +.project | |
53 | +# JDT-specific (Eclipse Java Development Tools) | |
54 | +.classpath | |
55 | + | |
... | ... |
README.md
0 → 100644
1 | +++ a/README.md | |
1 | +dili-boss模块及功能范围: boss.diligrp.com | |
2 | + boss-shared 基础设施和共享模块(解决循环依赖问题,工具类引入,中间件服务mq/redis/mysql等配置,第三方框架的封装和配置,全局异常拦截配置 ID生成器) | |
3 | + boss-auth 用户认证授权,系统用户管理等 | |
4 | + boss-admin 基础数据维护, 运营参数设置,帮助中心 | |
5 | + boss-support 运营支持类功能:审核功能,用户数据查询 | |
6 | + boss-report 运营数据报表 | |
7 | + boss-ui 前端VUE页面 | |
8 | + boss-boot 父工程(springboot打包,系统对外提供开放接口) | |
9 | + | |
10 | +模块依赖: 后期根据实际需求调整模块依赖 | |
11 | + boss-auth -> boss-shared | |
12 | + boss-admin -> boss-auth | |
13 | + boss-support -> boss-admin | |
14 | + boss-report -> boss-admin | |
15 | + boss-boot -> boss-admin boss-support boss-report | |
16 | + | |
17 | +项目结构 | |
18 | + com.diligrp.boss.xxxx - 模块spring配置xxxxConfiguration(Spring组件扫描配置/MybatisMapper扫描配置)ErrorCode Constants | |
19 | + com.diligrp.boss.xxxx.controller - 后台接口 | |
20 | + com.diligrp.boss.xxxx.api - 移动端接口 | |
21 | + com.diligrp.boss.xxxx.service | |
22 | + com.diligrp.boss.xxxx.dao | |
23 | + com.diligrp.boss.xxxx.exception | |
24 | + com.diligrp.boss.xxxx.domain | |
25 | + com.diligrp.boss.xxxx.model | |
26 | + com.diligrp.boss.xxxx.type | |
27 | + com.diligrp.boss.xxxx.util | |
28 | + resource/com.diligrp.boss.dao.mapper - mybatis mapper文件 | |
29 | + | |
30 | + 系统对第三方系统提供接口通过erp-boot模块api包 | |
31 | + 模块内部后台服务接口使用controller包 | |
32 | + 所有数据模型类放入com.diligrp.boss.xxxx.model下,所有域模型类(VO DTO)放入com.diligrp.boss.xxxx.domain下 | |
33 | + 所有数据模型类须继承BaseDo类,进一步规范数据表设计:需包含id version created_time modified_time | |
34 | + 所有枚举类型放入com.diligrp.boss.xxxx.type下,枚举类定义请提供code/name属性,参见com.diligrp.boss.shared.type.Gender | |
35 | + 所有自定义工具类放入com.diligrp.boss.xxxx.util下,如果各模块共用则放erp-shared模块下 | |
36 | + 所有异常类继承PlatformServiceException(提供了错误码和是否打印异常栈信息功能),并放入com.diligrp.boss.xxxx.exception下 | |
37 | + 每个模块的常量类请放在模块根目录下,如通用常量请放入boss-shared模块下 | |
38 | + 错误码为6位,每个模块的错误类ErrorCode且放入模块根目录,错误码应唯一且独特如前三位为模块标识,公共错误码参见com.diligrp.boss.shared.ErrorCode | |
39 | + | |
40 | +工具类 | |
41 | + 参见:com.diligrp.boss.shared.util.* com.diligrp.boss.shared.security.* | |
42 | + 包括:JsonUtils CurrencyUtils DateUtils RandomUtils AssertUtils HexUtils AesCipher RsaCipher ShaCipher KeyStoreUtils等等 | |
43 | + | |
44 | +技术要求 | |
45 | + JDK17 SpringCould SpringBoot 3版本 | |
46 | + 编译工具:gradle | |
47 | + 第三方库尽量使用springboot默认推荐,如:Jackson Lettuce;springboot工具集中没有推荐的第三方库,引入时请在合适模块中进行 | |
48 | + 已在boss-shared中完成Jackson配置,包括Spring DataBinding,且额外提供了Jackson工具类JsonUtils | |
49 | + 已在boss-shared中已完成Redis基础配置Lettuce,可直接使用StringRedisTemplate,如需进行进一步封装配置请在合适的模块中配置,如需Redis分布式锁,可考虑引入Redission | |
50 | + 已在boss-shared中已完成Mybatis基础配置,使用MapperScan完成mapper文件的扫描,不用plus,可用mybatis分页插件 | |
51 | + 已在boss-shared中完成MQ基础配置RabbitMQ,可直接进行使用RabbitTemplate且可进行Queue Exchange和消息监听器的配置 | |
52 | + 外部第三方jar放入dili-boss/libs | |
53 | + 新技术框架的引入不以个人熟悉为重点考量标准,以技术框架的通用型和稳定性为考量标准 | |
54 | + | |
55 | +数据库脚本要求 | |
56 | + 维护全量(dili-boss/scripts)和增量脚本(scripts/upgrade) | |
57 | + 维护增量脚本,需同时修改全量脚本 | |
58 | + 所有建表SQL,每个字段需填写备注 | |
59 | + 通常情况下,每个表都需要包含三个字段id,version,created_time,modified_time | |
60 | + 每个模块的数据表,建议统一的前缀boss_**** | |
61 | + | |
62 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/api | |
63 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/controller | |
64 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/service | |
65 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/dao | |
66 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/exception | |
67 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/domain | |
68 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/model | |
69 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/type | |
70 | +mkdir -p src/main/java/com/diligrp/erp/$ModuleName/util | |
71 | +mkdir -p src/main/resources/com/diligrp/erp/dao/mapper | |
0 | 72 | \ No newline at end of file |
... | ... |
boss-admin/build.gradle
0 → 100644
boss-admin/src/main/java/com/diligrp/boss/admin/AdminConfiguration.java
0 → 100644
1 | +++ a/boss-admin/src/main/java/com/diligrp/boss/admin/AdminConfiguration.java | |
1 | +package com.diligrp.boss.admin; | |
2 | + | |
3 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
4 | +import org.mybatis.spring.annotation.MapperScan; | |
5 | +import org.springframework.context.annotation.ComponentScan; | |
6 | +import org.springframework.context.annotation.Configuration; | |
7 | + | |
8 | +@Configuration | |
9 | +@ComponentScan("com.diligrp.boss.admin") | |
10 | +@MapperScan(basePackages = {"com.diligrp.boss.admin.dao"}, markerInterface = MybatisMapperSupport.class) | |
11 | +public class AdminConfiguration { | |
12 | +} | |
... | ... |
boss-auth/build.gradle
0 → 100644
boss-auth/src/main/java/com/diligrp/boss/auth/AuthConfiguration.java
0 → 100644
1 | +++ a/boss-auth/src/main/java/com/diligrp/boss/auth/AuthConfiguration.java | |
1 | +package com.diligrp.boss.auth; | |
2 | + | |
3 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
4 | +import org.mybatis.spring.annotation.MapperScan; | |
5 | +import org.springframework.context.annotation.ComponentScan; | |
6 | +import org.springframework.context.annotation.Configuration; | |
7 | + | |
8 | +@Configuration | |
9 | +@ComponentScan("com.diligrp.boss.auth") | |
10 | +@MapperScan(basePackages = {"com.diligrp.boss.auth.dao"}, markerInterface = MybatisMapperSupport.class) | |
11 | +public class AuthConfiguration { | |
12 | +} | |
... | ... |
boss-boot/build.gradle
0 → 100644
1 | +++ a/boss-boot/build.gradle | |
1 | +group = 'com.diligrp' | |
2 | +archivesBaseName = 'boss-boot' | |
3 | + | |
4 | +jar.enabled = false | |
5 | +bootJar { | |
6 | + enabled = true | |
7 | + archiveBaseName = 'boss-boot' | |
8 | +} | |
9 | + | |
10 | +dependencies { | |
11 | + implementation project(':boss-admin') | |
12 | + implementation project(':boss-support') | |
13 | + implementation project(':boss-report') | |
14 | +} | |
0 | 15 | \ No newline at end of file |
... | ... |
boss-boot/src/main/java/com/diligrp/boss/boot/BootConfiguration.java
0 → 100644
1 | +++ a/boss-boot/src/main/java/com/diligrp/boss/boot/BootConfiguration.java | |
1 | +package com.diligrp.boss.boot; | |
2 | + | |
3 | +import org.springframework.context.annotation.ComponentScan; | |
4 | +import org.springframework.context.annotation.Configuration; | |
5 | + | |
6 | +@Configuration | |
7 | +@ComponentScan("com.diligrp.boss.boot") | |
8 | +public class BootConfiguration { | |
9 | +} | |
... | ... |
boss-boot/src/main/java/com/diligrp/boss/boot/BossServiceBootstrap.java
0 → 100644
1 | +++ a/boss-boot/src/main/java/com/diligrp/boss/boot/BossServiceBootstrap.java | |
1 | +package com.diligrp.boss.boot; | |
2 | + | |
3 | +import com.diligrp.boss.admin.AdminConfiguration; | |
4 | +import com.diligrp.boss.auth.AuthConfiguration; | |
5 | +import com.diligrp.boss.report.ReportConfiguration; | |
6 | +import com.diligrp.boss.shared.SharedConfiguration; | |
7 | +import com.diligrp.boss.support.SupportConfiguration; | |
8 | +import org.springframework.boot.SpringApplication; | |
9 | +import org.springframework.boot.SpringBootConfiguration; | |
10 | +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; | |
11 | +import org.springframework.context.annotation.Import; | |
12 | + | |
13 | +@SpringBootConfiguration | |
14 | +@EnableAutoConfiguration | |
15 | +@Import({BootConfiguration.class, SharedConfiguration.class, AuthConfiguration.class, AdminConfiguration.class, | |
16 | + SupportConfiguration.class, ReportConfiguration.class}) | |
17 | +public class BossServiceBootstrap { | |
18 | + public static void main(String[] args) { | |
19 | + SpringApplication.run(BossServiceBootstrap.class, args); | |
20 | + } | |
21 | +} | |
0 | 22 | \ No newline at end of file |
... | ... |
boss-boot/src/main/java/com/diligrp/boss/boot/controller/BossOpenApiController.java
0 → 100644
1 | +++ a/boss-boot/src/main/java/com/diligrp/boss/boot/controller/BossOpenApiController.java | |
1 | +package com.diligrp.boss.boot.controller; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.Message; | |
4 | +import com.diligrp.boss.shared.uid.KeyGenerator; | |
5 | +import com.diligrp.boss.shared.uid.KeyGeneratorManager; | |
6 | +import jakarta.annotation.Resource; | |
7 | +import org.springframework.web.bind.annotation.RequestMapping; | |
8 | +import org.springframework.web.bind.annotation.RestController; | |
9 | + | |
10 | +@RestController | |
11 | +@RequestMapping(value = "/api") | |
12 | +public class BossOpenApiController { | |
13 | + @Resource | |
14 | + private KeyGeneratorManager keyGeneratorManager; | |
15 | + | |
16 | + @RequestMapping(value = "/uid/get.do") | |
17 | + public Message<?> testUid() { | |
18 | + KeyGenerator keyGenerator = keyGeneratorManager.getKeyGenerator("TEST_KEY"); | |
19 | + return Message.success(keyGenerator.nextId()); | |
20 | + } | |
21 | +} | |
... | ... |
boss-boot/src/main/resources/application-dev.properties
0 → 100644
1 | +++ a/boss-boot/src/main/resources/application-dev.properties | |
1 | +#Datasource configuration | |
2 | +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver | |
3 | +spring.datasource.url=jdbc:mysql://mysql.diligrp.com:3306/dili_boss?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 | |
4 | +spring.datasource.username=root | |
5 | +spring.datasource.password=123456 | |
6 | +spring.datasource.type=com.zaxxer.hikari.HikariDataSource | |
7 | +spring.datasource.hikari.pool-name=BossHikariPool | |
8 | +spring.datasource.hikari.minimum-idle=4 | |
9 | +spring.datasource.hikari.maximum-pool-size=60 | |
10 | +spring.datasource.hikari.idle-timeout=120000 | |
11 | +spring.datasource.hikari.max-lifetime=900000 | |
12 | +spring.datasource.hikari.connection-timeout=15000 | |
13 | +spring.datasource.hikari.connection-test-query=SELECT 1 | |
14 | + | |
15 | +#Redis configuration | |
16 | +spring.data.redis.host=redis.diligrp.com | |
17 | +spring.data.redis.port=6379 | |
18 | +spring.data.redis.database=8 | |
19 | +#spring.data.redis.username= | |
20 | +#spring.data.redis.password= | |
21 | +spring.data.redis.connect-timeout=15000 | |
22 | +spring.data.redis.timeout=30000 | |
23 | +#spring.data.redis.lettuce.pool.enabled=false | |
24 | + | |
25 | +#RabbitMQ configuration | |
26 | +spring.rabbitmq.host=rabbitmq.diligrp.com | |
27 | +spring.rabbitmq.port=5672 | |
28 | +spring.rabbitmq.username=admin | |
29 | +spring.rabbitmq.password=123456 | |
30 | +spring.rabbitmq.virtual-host=/ | |
0 | 31 | \ No newline at end of file |
... | ... |
boss-boot/src/main/resources/application-prod.properties
0 → 100644
1 | +++ a/boss-boot/src/main/resources/application-prod.properties | |
1 | +#Datasource configuration | |
2 | +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver | |
3 | +spring.datasource.url=jdbc:mysql://mysql.diligrp.com:3306/dili_boss?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 | |
4 | +spring.datasource.username=root | |
5 | +spring.datasource.password=123456 | |
6 | +spring.datasource.type=com.zaxxer.hikari.HikariDataSource | |
7 | +spring.datasource.hikari.pool-name=BossHikariPool | |
8 | +spring.datasource.hikari.minimum-idle=4 | |
9 | +spring.datasource.hikari.maximum-pool-size=60 | |
10 | +spring.datasource.hikari.idle-timeout=120000 | |
11 | +spring.datasource.hikari.max-lifetime=900000 | |
12 | +spring.datasource.hikari.connection-timeout=15000 | |
13 | +spring.datasource.hikari.connection-test-query=SELECT 1 | |
14 | + | |
15 | +#Redis configuration | |
16 | +spring.data.redis.host=redis.diligrp.com | |
17 | +spring.data.redis.port=6379 | |
18 | +spring.data.redis.database=8 | |
19 | +#spring.data.redis.username= | |
20 | +#spring.data.redis.password= | |
21 | +spring.data.redis.connect-timeout=15000 | |
22 | +spring.data.redis.timeout=30000 | |
23 | +#spring.data.redis.lettuce.pool.enabled=false | |
24 | + | |
25 | +#RabbitMQ configuration | |
26 | +spring.rabbitmq.host=rabbitmq.diligrp.com | |
27 | +spring.rabbitmq.port=5672 | |
28 | +spring.rabbitmq.username=admin | |
29 | +spring.rabbitmq.password=123456 | |
30 | +spring.rabbitmq.virtual-host=/ | |
0 | 31 | \ No newline at end of file |
... | ... |
boss-boot/src/main/resources/application-test.properties
0 → 100644
1 | +++ a/boss-boot/src/main/resources/application-test.properties | |
1 | +#Datasource configuration | |
2 | +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver | |
3 | +spring.datasource.url=jdbc:mysql://mysql.diligrp.com:3306/dili_boss?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 | |
4 | +spring.datasource.username=root | |
5 | +spring.datasource.password=123456 | |
6 | +spring.datasource.type=com.zaxxer.hikari.HikariDataSource | |
7 | +spring.datasource.hikari.pool-name=BossHikariPool | |
8 | +spring.datasource.hikari.minimum-idle=4 | |
9 | +spring.datasource.hikari.maximum-pool-size=60 | |
10 | +spring.datasource.hikari.idle-timeout=120000 | |
11 | +spring.datasource.hikari.max-lifetime=900000 | |
12 | +spring.datasource.hikari.connection-timeout=15000 | |
13 | +spring.datasource.hikari.connection-test-query=SELECT 1 | |
14 | + | |
15 | +#Redis configuration | |
16 | +spring.data.redis.host=redis.diligrp.com | |
17 | +spring.data.redis.port=6379 | |
18 | +spring.data.redis.database=8 | |
19 | +#spring.data.redis.username= | |
20 | +#spring.data.redis.password= | |
21 | +spring.data.redis.connect-timeout=15000 | |
22 | +spring.data.redis.timeout=30000 | |
23 | +#spring.data.redis.lettuce.pool.enabled=false | |
24 | + | |
25 | +#RabbitMQ configuration | |
26 | +spring.rabbitmq.host=rabbitmq.diligrp.com | |
27 | +spring.rabbitmq.port=5672 | |
28 | +spring.rabbitmq.username=admin | |
29 | +spring.rabbitmq.password=123456 | |
30 | +spring.rabbitmq.virtual-host=/ | |
0 | 31 | \ No newline at end of file |
... | ... |
boss-boot/src/main/resources/application.properties
0 → 100644
1 | +++ a/boss-boot/src/main/resources/application.properties | |
1 | +#Logback configuration | |
2 | +logging.config=classpath:logback-spring.xml | |
3 | + | |
4 | +#Mybatis configuration | |
5 | +mybatis.configuration.cache-enabled=true | |
6 | +mybatis.configuration.multiple-result-sets-enabled=true | |
7 | +mybatis.configuration.map-underscore-to-camel-case=true | |
8 | +mybatis.configuration.use-column-label=true | |
9 | +mybatis.configuration.default-statement-timeout=25000 | |
10 | +mybatis.mapper-locations=classpath*:com/diligrp/boss/dao/mapper/**/*.xml | |
11 | +mybatis.configuration.default-enum-type-handler=com.diligrp.boss.shared.mybatis.GenericEnumTypeHandler | |
0 | 12 | \ No newline at end of file |
... | ... |
boss-boot/src/main/resources/bootstrap.properties
0 → 100644
1 | +++ a/boss-boot/src/main/resources/bootstrap.properties | |
1 | +server.port=8684 | |
2 | +server.servlet.context-path=/ | |
3 | +server.servlet.encoding.charset=UTF-8 | |
4 | +server.servlet.encoding.force=true | |
5 | + | |
6 | +spring.profiles.active=dev | |
7 | +spring.application.name=boss-service | |
0 | 8 | \ No newline at end of file |
... | ... |
boss-boot/src/main/resources/logback-spring.xml
0 → 100644
1 | +++ a/boss-boot/src/main/resources/logback-spring.xml | |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<configuration> | |
3 | + <!-- 日志名称 --> | |
4 | + <property name="LOG_NAME" value="boss-boot" /> | |
5 | + <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 --> | |
6 | + <property name="LOG_HOME" value="logs" /> | |
7 | + | |
8 | + <!-- springProperty读取springboot配置属性 --> | |
9 | + <springProperty scope="context" name="build.profile.id" source="spring.profiles.active" /> | |
10 | + | |
11 | + <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> | |
12 | + | |
13 | + <!-- 日志控制台输出 --> | |
14 | + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | |
15 | + <encoder> | |
16 | + <pattern>%d %-5level [${LOG_NAME}-${build.profile.id}] [%t] [%c:%L] -| %msg%n</pattern> | |
17 | + </encoder> | |
18 | + </appender> | |
19 | + | |
20 | + <!-- 日志文件输出 --> | |
21 | + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |
22 | + <file>${LOG_HOME}/${LOG_NAME}.log</file> | |
23 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | |
24 | + <!--日志文件输出的文件名 --> | |
25 | + <fileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/${LOG_NAME}_%i.log.zip</fileNamePattern> | |
26 | + <!--日志文件保留天数(FileNamePattern中的%d 格式有关,如果yyyy-MM-dd 则是天数) --> | |
27 | + <maxHistory>10</maxHistory> | |
28 | + <!--日志文件最大的大小 --> | |
29 | + <maxFileSize>10MB</maxFileSize> | |
30 | + </rollingPolicy> | |
31 | + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> | |
32 | + <pattern>%d %-5level [${LOG_NAME}-${build.profile.id}] [%t] [%c:%L]-| %msg%n</pattern> | |
33 | + </encoder> | |
34 | + </appender> | |
35 | + | |
36 | + <!-- 开发环境 --> | |
37 | + <springProfile name="dev"> | |
38 | + <root level="INFO"> | |
39 | + <appender-ref ref="CONSOLE" /> | |
40 | + </root> | |
41 | + | |
42 | + <logger name="com.diligrp.boss" level="DEBUG" additivity="false"> | |
43 | + <appender-ref ref="CONSOLE" /> | |
44 | + </logger> | |
45 | + | |
46 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | |
47 | + <appender-ref ref="CONSOLE" /> | |
48 | + </logger> | |
49 | + </springProfile> | |
50 | + | |
51 | + <!-- 测试环境 --> | |
52 | + <springProfile name="test"> | |
53 | + <root level="INFO"> | |
54 | + <appender-ref ref="CONSOLE" /> | |
55 | + </root> | |
56 | + | |
57 | + <logger name="com.diligrp.boss" level="DEBUG"> | |
58 | + <appender-ref ref="FILE" /> | |
59 | + </logger> | |
60 | + | |
61 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | |
62 | + <appender-ref ref="CONSOLE" /> | |
63 | + <appender-ref ref="FILE" /> | |
64 | + </logger> | |
65 | + </springProfile> | |
66 | + | |
67 | + <!-- 灰度、生产环境 --> | |
68 | + <springProfile name="pre,prod"> | |
69 | + <root level="INFO"> | |
70 | + <appender-ref ref="CONSOLE" /> | |
71 | + </root> | |
72 | + | |
73 | + <logger name="com.diligrp.boss" level="DEBUG"> | |
74 | + <appender-ref ref="FILE" /> | |
75 | + </logger> | |
76 | + | |
77 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | |
78 | + <appender-ref ref="CONSOLE" /> | |
79 | + <appender-ref ref="FILE" /> | |
80 | + </logger> | |
81 | + | |
82 | + <!-- 单独给子模块指定日志配置时,请注意additivity的使用 | |
83 | + <logger name="com.diligrp.boss.admin" level="debug" additivity="false"> | |
84 | + <appender-ref ref="CONSOLE" /> | |
85 | + <appender-ref ref="FILE" /> | |
86 | + </logger> | |
87 | + --> | |
88 | + </springProfile> | |
89 | +</configuration> | |
0 | 90 | \ No newline at end of file |
... | ... |
boss-report/build.gradle
0 → 100644
boss-report/src/main/java/com/diligrp/boss/report/ReportConfiguration.java
0 → 100644
1 | +++ a/boss-report/src/main/java/com/diligrp/boss/report/ReportConfiguration.java | |
1 | +package com.diligrp.boss.report; | |
2 | + | |
3 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
4 | +import org.mybatis.spring.annotation.MapperScan; | |
5 | +import org.springframework.context.annotation.ComponentScan; | |
6 | +import org.springframework.context.annotation.Configuration; | |
7 | + | |
8 | +@Configuration | |
9 | +@ComponentScan("com.diligrp.boss.report") | |
10 | +@MapperScan(basePackages = {"com.diligrp.boss.report.dao"}, markerInterface = MybatisMapperSupport.class) | |
11 | +public class ReportConfiguration { | |
12 | +} | |
... | ... |
boss-shared/build.gradle
0 → 100644
1 | +++ a/boss-shared/build.gradle | |
1 | + group = 'com.diligrp' | |
2 | +archivesBaseName = 'boss-shared' | |
3 | + | |
4 | +dependencies { | |
5 | +// api 'org.springframework.boot:spring-boot-starter-data-mongodb' | |
6 | + api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2' | |
7 | + api 'org.springframework.boot:spring-boot-starter-data-redis' | |
8 | + //api 'org.redisson:redisson-spring-boot-starter:3.23.1' | |
9 | + api 'org.springframework.boot:spring-boot-starter-amqp' | |
10 | + api 'org.springframework.cloud:spring-cloud-starter-loadbalancer' | |
11 | + api 'org.springframework.cloud:spring-cloud-starter-openfeign' | |
12 | + api 'com.github.ben-manes.caffeine:caffeine:3.1.8' | |
13 | + | |
14 | + runtimeOnly 'mysql:mysql-connector-java:8.0.33' | |
15 | +} | |
0 | 16 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/Constants.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/Constants.java | |
1 | +package com.diligrp.boss.shared; | |
2 | + | |
3 | +public final class Constants { | |
4 | + public static final String SIGN_ALGORITHM = "SHA1WithRSA"; | |
5 | + | |
6 | + public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; | |
7 | + | |
8 | + public static final String DATE_FORMAT = "yyyy-MM-dd"; | |
9 | + | |
10 | + public static final String TIME_FORMAT = "HH:mm:ss"; | |
11 | + | |
12 | + public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1; | |
13 | + | |
14 | + public static final int MAX_POOL_SIZE = 200; | |
15 | +} | |
0 | 16 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/ErrorCode.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/ErrorCode.java | |
1 | +package com.diligrp.boss.shared; | |
2 | + | |
3 | +/** | |
4 | + * 系统错误码列表 - 错误码前三位用于区分模块 | |
5 | + */ | |
6 | +public class ErrorCode { | |
7 | + // 系统未知异常 | |
8 | + public static final int SYSTEM_UNKNOWN_ERROR = 100000; | |
9 | + // 无效参数错误 | |
10 | + public static final int ILLEGAL_ARGUMENT_ERROR = 100001; | |
11 | + // 访问未授权 | |
12 | + public static final int UNAUTHORIZED_ACCESS_ERROR = 100002; | |
13 | + // 操作不允许 | |
14 | + public static final int OPERATION_NOT_ALLOWED = 100003; | |
15 | + // 对象不存在 | |
16 | + public static final int OBJECT_NOT_FOUND = 100004; | |
17 | + // 对象已存在 | |
18 | + public static final int OBJECT_ALREADY_EXISTS = 100005; | |
19 | + // 远程服务访问错误 | |
20 | + public static final int SERVICE_ACCESS_ERROR = 101000; | |
21 | + | |
22 | + public static final String MESSAGE_UNKNOWN_ERROR = "基础服务系统未知异常,请联系管理员"; | |
23 | + | |
24 | + public static final String MESSAGE_ACCESS_DENIED = "未授权的系统访问"; | |
25 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/SharedConfiguration.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/SharedConfiguration.java | |
1 | +package com.diligrp.boss.shared; | |
2 | + | |
3 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
4 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
5 | +import com.diligrp.boss.shared.security.RsaCipher; | |
6 | +import com.diligrp.boss.shared.util.JsonUtils; | |
7 | +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | |
8 | +import org.mybatis.spring.annotation.MapperScan; | |
9 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | |
10 | +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; | |
11 | +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; | |
12 | +import org.springframework.context.annotation.Bean; | |
13 | +import org.springframework.context.annotation.ComponentScan; | |
14 | +import org.springframework.context.annotation.Configuration; | |
15 | +import org.springframework.core.convert.converter.Converter; | |
16 | +import org.springframework.data.redis.connection.RedisConnectionFactory; | |
17 | +import org.springframework.data.redis.core.RedisTemplate; | |
18 | +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | |
19 | +import org.springframework.data.redis.serializer.StringRedisSerializer; | |
20 | +import org.springframework.stereotype.Component; | |
21 | +import org.springframework.util.StringUtils; | |
22 | + | |
23 | +import java.security.PrivateKey; | |
24 | +import java.security.PublicKey; | |
25 | +import java.text.SimpleDateFormat; | |
26 | +import java.time.LocalDate; | |
27 | +import java.time.LocalDateTime; | |
28 | +import java.time.LocalTime; | |
29 | +import java.time.format.DateTimeFormatter; | |
30 | +import java.util.Date; | |
31 | + | |
32 | +@Configuration | |
33 | +@ComponentScan("com.diligrp.boss.shared") | |
34 | +@MapperScan(basePackages = {"com.diligrp.boss.shared.dao"}, markerInterface = MybatisMapperSupport.class) | |
35 | +public class SharedConfiguration { | |
36 | + @Bean | |
37 | + public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { | |
38 | + RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); | |
39 | + template.setConnectionFactory(factory); | |
40 | + template.setKeySerializer(new StringRedisSerializer()); | |
41 | + template.setHashKeySerializer(new StringRedisSerializer()); | |
42 | + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); | |
43 | + template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); | |
44 | + template.afterPropertiesSet(); | |
45 | + return template; | |
46 | + } | |
47 | + | |
48 | + // Jackson DataBinding所有配置 | |
49 | + @Bean | |
50 | + @ConditionalOnClass(JavaTimeModule.class) | |
51 | + public Jackson2ObjectMapperBuilderCustomizer customizeJacksonConfig() { | |
52 | + return JsonUtils::initObjectMapperBuilder; | |
53 | + } | |
54 | + | |
55 | + @Bean | |
56 | + public Converter<String, LocalDateTime> localDateTimeConverter() { | |
57 | + // 不能使用lambda表达式,否则导致springboot启动问题 | |
58 | + return new Converter<String, LocalDateTime>() { | |
59 | + @Override | |
60 | + public LocalDateTime convert(String source) { | |
61 | + try { | |
62 | + return StringUtils.hasText(source) ? LocalDateTime.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT)) : null; | |
63 | + } catch (Exception ex) { | |
64 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalDateTime", source), ex); | |
65 | + } | |
66 | + } | |
67 | + }; | |
68 | + } | |
69 | + | |
70 | + @Bean | |
71 | + public Converter<String, LocalDate> localDateConverter() { | |
72 | + // 不能使用lambda表达式,否则导致springboot启动问题 | |
73 | + return new Converter<String, LocalDate>() { | |
74 | + @Override | |
75 | + public LocalDate convert(String source) { | |
76 | + try { | |
77 | + return StringUtils.hasText(source) ? LocalDate.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_FORMAT)) : null; | |
78 | + } catch (Exception ex) { | |
79 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalDate", source), ex); | |
80 | + } | |
81 | + } | |
82 | + }; | |
83 | + } | |
84 | + | |
85 | + @Bean | |
86 | + public Converter<String, LocalTime> localTimeConverter() { | |
87 | + // 不能使用lambda表达式,否则导致springboot启动问题 | |
88 | + return new Converter<String, LocalTime>() { | |
89 | + @Override | |
90 | + public LocalTime convert(String source) { | |
91 | + try { | |
92 | + return StringUtils.hasText(source) ? LocalTime.parse(source, DateTimeFormatter.ofPattern(Constants.TIME_FORMAT)) : null; | |
93 | + } catch (Exception ex) { | |
94 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalTime", source), ex); | |
95 | + } | |
96 | + } | |
97 | + }; | |
98 | + } | |
99 | + | |
100 | + @Bean | |
101 | + public Converter<String, Date> dateConverter() { | |
102 | + // 不能使用lambda表达式,否则导致springboot启动问题 | |
103 | + return new Converter<String, Date>() { | |
104 | + @Override | |
105 | + public Date convert(String source) { | |
106 | + try { | |
107 | + return StringUtils.hasText(source) ? new SimpleDateFormat(Constants.DATE_TIME_FORMAT).parse(source) : null; | |
108 | + } catch (Exception ex) { | |
109 | + throw new IllegalArgumentException(String.format("Error parse %s to Date", source), ex); | |
110 | + } | |
111 | + } | |
112 | + }; | |
113 | + } | |
114 | + | |
115 | + @Component | |
116 | + @ConfigurationPropertiesBinding | |
117 | + public class PrivateKeyConverter implements Converter<String, PrivateKey> { | |
118 | + @Override | |
119 | + public PrivateKey convert(String source) { | |
120 | + try { | |
121 | + return RsaCipher.getPrivateKey(source); | |
122 | + } catch (Exception ex) { | |
123 | + throw new PlatformServiceException("privateKey configuration failed", ex); | |
124 | + } | |
125 | + } | |
126 | + } | |
127 | + | |
128 | + @Component | |
129 | + @ConfigurationPropertiesBinding | |
130 | + public class PublicKeyConverter implements Converter<String, PublicKey> { | |
131 | + @Override | |
132 | + public PublicKey convert(String source) { | |
133 | + try { | |
134 | + return RsaCipher.getPublicKey(source); | |
135 | + } catch (Exception ex) { | |
136 | + throw new PlatformServiceException("publicKey configuration failed", ex); | |
137 | + } | |
138 | + } | |
139 | + } | |
140 | +} | |
0 | 141 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/codec/ByteDecoder.java
0 → 100644
boss-shared/src/main/java/com/diligrp/boss/shared/codec/ByteEncoder.java
0 → 100644
boss-shared/src/main/java/com/diligrp/boss/shared/codec/StringCodec.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/codec/StringCodec.java | |
1 | +package com.diligrp.boss.shared.codec; | |
2 | + | |
3 | +import java.nio.charset.StandardCharsets; | |
4 | + | |
5 | +public final class StringCodec { | |
6 | + public static ByteEncoder<String> getEncoder() { | |
7 | + return StringEncoder.INSTANCE; | |
8 | + } | |
9 | + | |
10 | + public static ByteDecoder<String> getDecoder() { | |
11 | + return StringDecoder.INSTANCE; | |
12 | + } | |
13 | + | |
14 | + static class StringEncoder implements ByteEncoder<String> { | |
15 | + | |
16 | + static final ByteEncoder<String> INSTANCE = new StringEncoder(); | |
17 | + | |
18 | + @Override | |
19 | + public byte[] encode(String payload) { | |
20 | + return payload.getBytes(StandardCharsets.UTF_8); | |
21 | + } | |
22 | + } | |
23 | + | |
24 | + static class StringDecoder implements ByteDecoder<String> { | |
25 | + | |
26 | + static final ByteDecoder<String> INSTANCE = new StringDecoder(); | |
27 | + | |
28 | + @Override | |
29 | + public String decode(byte[] payload) { | |
30 | + return new String(payload, StandardCharsets.UTF_8); | |
31 | + } | |
32 | + } | |
33 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/dao/SequenceKeyDao.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/dao/SequenceKeyDao.java | |
1 | +package com.diligrp.boss.shared.dao; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.PersistentSequenceKey; | |
4 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
5 | +import org.springframework.stereotype.Repository; | |
6 | + | |
7 | +import java.util.Optional; | |
8 | + | |
9 | +/** | |
10 | + * KeySequence数据操作 | |
11 | + * | |
12 | + */ | |
13 | +@Repository("sequenceKeyDao") | |
14 | +public interface SequenceKeyDao extends MybatisMapperSupport { | |
15 | + /** | |
16 | + * 注册SequenceKey | |
17 | + */ | |
18 | + void insertSequenceKey(PersistentSequenceKey sequenceKey); | |
19 | + | |
20 | + /** | |
21 | + * 查询指定的SequenceKey | |
22 | + * | |
23 | + * @param key - SequenceKey唯一标识 | |
24 | + * @return SequenceKey | |
25 | + */ | |
26 | + Optional<PersistentSequenceKey> findSequenceKey(String key); | |
27 | + | |
28 | + /** | |
29 | + * 根据KeyId查询SequenceKey | |
30 | + * | |
31 | + * @param id - KeyId | |
32 | + * @return SequenceKey | |
33 | + */ | |
34 | + Optional<PersistentSequenceKey> findSequenceKeyById(Long id); | |
35 | + | |
36 | + /** | |
37 | + * 悲观锁实现 - 根据数据库主键锁定数据记录 | |
38 | + * | |
39 | + * @param id - KeyId | |
40 | + * @return SequenceKey | |
41 | + */ | |
42 | + Optional<PersistentSequenceKey> lockSequenceKey(Long id); | |
43 | + | |
44 | + /** | |
45 | + * 悲观锁解锁实现 - 根据数据库主键解锁数据记录 | |
46 | + * | |
47 | + * @param sequenceKey - 参数列表:id/newValue/expiredOn | |
48 | + * @return 1-更新成功,0-更新失败 | |
49 | + */ | |
50 | + int unlockSequenceKey(PersistentSequenceKey sequenceKey); | |
51 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/AsyncMessage.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/AsyncMessage.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import com.diligrp.boss.shared.util.JsonUtils; | |
4 | + | |
5 | +/** | |
6 | + * MQ异步消息模型 | |
7 | + */ | |
8 | +public class AsyncMessage { | |
9 | + // 消息类型 | |
10 | + private Integer type; | |
11 | + // 消息体 | |
12 | + private String payload; | |
13 | + // 消息参数 | |
14 | + private String params; | |
15 | + | |
16 | + public static AsyncMessage of(Integer type, String payload, String params) { | |
17 | + AsyncMessage message = new AsyncMessage(); | |
18 | + message.type = type; | |
19 | + message.payload = payload; | |
20 | + message.params = params; | |
21 | + return message; | |
22 | + } | |
23 | + | |
24 | + public static AsyncMessage from(String message) { | |
25 | + return JsonUtils.fromJsonString(message, AsyncMessage.class); | |
26 | + } | |
27 | + | |
28 | + public Integer getType() { | |
29 | + return type; | |
30 | + } | |
31 | + | |
32 | + public String getPayload() { | |
33 | + return payload; | |
34 | + } | |
35 | + | |
36 | + public String getParams() { | |
37 | + return params; | |
38 | + } | |
39 | + | |
40 | + public String toString() { | |
41 | + return JsonUtils.toJsonString(this); | |
42 | + } | |
43 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/BaseDo.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/BaseDo.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import java.time.LocalDateTime; | |
4 | + | |
5 | +public class BaseDo { | |
6 | + // 数据库主键 | |
7 | + protected Long id; | |
8 | + // 数据版本 | |
9 | + protected Integer version; | |
10 | + // 创建时间 | |
11 | + protected LocalDateTime createdTime; | |
12 | + // 修改时间 | |
13 | + protected LocalDateTime modifiedTime; | |
14 | + | |
15 | + public Long getId() { | |
16 | + return id; | |
17 | + } | |
18 | + | |
19 | + public void setId(Long id) { | |
20 | + this.id = id; | |
21 | + } | |
22 | + | |
23 | + public Integer getVersion() { | |
24 | + return version; | |
25 | + } | |
26 | + | |
27 | + public void setVersion(Integer version) { | |
28 | + this.version = version; | |
29 | + } | |
30 | + | |
31 | + public LocalDateTime getCreatedTime() { | |
32 | + return createdTime; | |
33 | + } | |
34 | + | |
35 | + public void setCreatedTime(LocalDateTime createdTime) { | |
36 | + this.createdTime = createdTime; | |
37 | + } | |
38 | + | |
39 | + public LocalDateTime getModifiedTime() { | |
40 | + return modifiedTime; | |
41 | + } | |
42 | + | |
43 | + public void setModifiedTime(LocalDateTime modifiedTime) { | |
44 | + this.modifiedTime = modifiedTime; | |
45 | + } | |
46 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/ContainerSupport.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/ContainerSupport.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import java.util.HashMap; | |
4 | +import java.util.List; | |
5 | +import java.util.Optional; | |
6 | + | |
7 | +public abstract class ContainerSupport extends HashMap<String, Object> { | |
8 | + public ContainerSupport attach(Object object) { | |
9 | + put(object.getClass().getName(), object); | |
10 | + return this; | |
11 | + } | |
12 | + | |
13 | + public ContainerSupport attach(String key, Object object) { | |
14 | + put(key, object); | |
15 | + return this; | |
16 | + } | |
17 | + | |
18 | + public Long getLong(String param) { | |
19 | + Object value = get(param); | |
20 | + if (value != null) { | |
21 | + return value instanceof Long ? (Long)value : Long.parseLong(value.toString()); | |
22 | + } | |
23 | + return null; | |
24 | + } | |
25 | + | |
26 | + public Integer getInteger(String param) { | |
27 | + Object value = get(param); | |
28 | + if (value != null) { | |
29 | + return value instanceof Integer ? (Integer)value : Integer.parseInt(value.toString()); | |
30 | + } | |
31 | + return null; | |
32 | + } | |
33 | + | |
34 | + public String getString(String param) { | |
35 | + Object value = get(param); | |
36 | + return value != null ? value.toString() : null; | |
37 | + } | |
38 | + | |
39 | + public <T> T getObject(String param, Class<T> type) { | |
40 | + Object value = get(param); | |
41 | + return value == null ? null : type.cast(value); | |
42 | + } | |
43 | + | |
44 | + public <T> T getObject(Class<T> type) { | |
45 | + Object value = get(type.getName()); | |
46 | + return value == null ? null : type.cast(value); | |
47 | + } | |
48 | + | |
49 | + @SuppressWarnings("unchecked") | |
50 | + public <T> Optional<T> getObject(String param) { | |
51 | + Object value = get(param); | |
52 | + return Optional.ofNullable ((T) value); | |
53 | + } | |
54 | + | |
55 | + @SuppressWarnings("unchecked") | |
56 | + public <T> Optional<List<T>> getObjects(String param) { | |
57 | + Object value = get(param); | |
58 | + return Optional.ofNullable ((List<T>) value); | |
59 | + } | |
60 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/Message.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/Message.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | + | |
5 | +public class Message<T> { | |
6 | + protected static final int CODE_SUCCESS = 200; | |
7 | + protected static final int CODE_FAILURE = ErrorCode.SYSTEM_UNKNOWN_ERROR; | |
8 | + protected static final String MSG_SUCCESS = "success"; | |
9 | + | |
10 | + private Integer code; | |
11 | + private String message; | |
12 | + private T data; | |
13 | + | |
14 | + public Message() { | |
15 | + } | |
16 | + | |
17 | + public Integer getCode() { | |
18 | + return this.code; | |
19 | + } | |
20 | + | |
21 | + public void setCode(Integer code) { | |
22 | + this.code = code; | |
23 | + } | |
24 | + | |
25 | + public String getMessage() { | |
26 | + return this.message; | |
27 | + } | |
28 | + | |
29 | + public void setMessage(String message) { | |
30 | + this.message = message; | |
31 | + } | |
32 | + | |
33 | + public T getData() { | |
34 | + return this.data; | |
35 | + } | |
36 | + | |
37 | + public void setData(T data) { | |
38 | + this.data = data; | |
39 | + } | |
40 | + | |
41 | + public static Message<?> success() { | |
42 | + Message<?> result = new Message(); | |
43 | + result.code = 200; | |
44 | + result.message = "success"; | |
45 | + return result; | |
46 | + } | |
47 | + | |
48 | + public static <E> Message<E> success(E data) { | |
49 | + Message<E> result = new Message(); | |
50 | + result.code = 200; | |
51 | + result.data = data; | |
52 | + result.message = "success"; | |
53 | + return result; | |
54 | + } | |
55 | + | |
56 | + public static Message<?> failure(String message) { | |
57 | + Message<?> result = new Message(); | |
58 | + result.code = 1000; | |
59 | + result.message = message; | |
60 | + return result; | |
61 | + } | |
62 | + | |
63 | + public static Message<?> failure(int code, String message) { | |
64 | + Message<?> result = new Message(); | |
65 | + result.code = code; | |
66 | + result.message = message; | |
67 | + return result; | |
68 | + } | |
69 | +} | |
0 | 70 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/PageMessage.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/PageMessage.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import java.util.List; | |
4 | + | |
5 | +/** | |
6 | + * 分页数据模型 | |
7 | + */ | |
8 | +public class PageMessage<E> extends Message<List<E>>{ | |
9 | + // 总记录数 | |
10 | + private long total; | |
11 | + | |
12 | + public long getTotal() { | |
13 | + return total; | |
14 | + } | |
15 | + | |
16 | + public void setTotal(long total) { | |
17 | + this.total = total; | |
18 | + } | |
19 | + | |
20 | + public static <T> PageMessage<T> success(long total, List<T> data) { | |
21 | + PageMessage<T> page = new PageMessage<>(); | |
22 | + page.setCode(CODE_SUCCESS); | |
23 | + page.setTotal(total); | |
24 | + page.setData(data); | |
25 | + page.setMessage(MSG_SUCCESS); | |
26 | + return page; | |
27 | + } | |
28 | + | |
29 | + public static PageMessage<?> failure(String message) { | |
30 | + PageMessage<?> page = new PageMessage<>(); | |
31 | + page.setCode(CODE_FAILURE); | |
32 | + page.setTotal(0); | |
33 | + page.setData(null); | |
34 | + page.setMessage(message); | |
35 | + return page; | |
36 | + } | |
37 | + | |
38 | + public static PageMessage<?> failure(int code, String message) { | |
39 | + PageMessage<?> page = new PageMessage<>(); | |
40 | + page.setCode(code); | |
41 | + page.setTotal(0); | |
42 | + page.setData(null); | |
43 | + page.setMessage(message); | |
44 | + return page; | |
45 | + } | |
46 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/PageQuery.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/PageQuery.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +/** | |
4 | + * 分页查询领域模型 | |
5 | + */ | |
6 | +public class PageQuery { | |
7 | + // 起始行下标 | |
8 | + protected Integer start; | |
9 | + // 获取的记录行数 | |
10 | + protected Integer limit; | |
11 | + | |
12 | + public Integer getStart() { | |
13 | + return start; | |
14 | + } | |
15 | + | |
16 | + public void setStart(Integer start) { | |
17 | + this.start = start; | |
18 | + } | |
19 | + | |
20 | + public Integer getLimit() { | |
21 | + return limit; | |
22 | + } | |
23 | + | |
24 | + public void setLimit(Integer limit) { | |
25 | + this.limit = limit; | |
26 | + } | |
27 | + | |
28 | + /** | |
29 | + * 通过页号、每页记录数计算起始行下标 | |
30 | + */ | |
31 | + public void from(int pageNo, int pageSize) { | |
32 | + this.start = (pageNo - 1) * pageSize; | |
33 | + this.limit = pageSize; | |
34 | + } | |
35 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/PersistentSequenceKey.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/PersistentSequenceKey.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import java.time.LocalDate; | |
4 | + | |
5 | +/** | |
6 | + * KEY-ID生成器数据库模型 | |
7 | + */ | |
8 | +public class PersistentSequenceKey { | |
9 | + /** | |
10 | + * ID主键 | |
11 | + */ | |
12 | + private Long id; | |
13 | + /** | |
14 | + * KEY标识 | |
15 | + */ | |
16 | + private String key; | |
17 | + /** | |
18 | + * 名称 | |
19 | + */ | |
20 | + private String name; | |
21 | + /** | |
22 | + * 起始值 | |
23 | + */ | |
24 | + private Long value; | |
25 | + /** | |
26 | + * 步长 | |
27 | + */ | |
28 | + private Integer step; | |
29 | + /** | |
30 | + * ID格式 | |
31 | + */ | |
32 | + private String pattern; | |
33 | + /** | |
34 | + * 有效日期 | |
35 | + */ | |
36 | + private LocalDate expiredOn; | |
37 | + /** | |
38 | + * 当前日期-循环ID生成器使用 | |
39 | + */ | |
40 | + private LocalDate today; | |
41 | + /** | |
42 | + * 数据版本 | |
43 | + */ | |
44 | + private Integer version; | |
45 | + | |
46 | + public Long getId() { | |
47 | + return id; | |
48 | + } | |
49 | + | |
50 | + public void setId(Long id) { | |
51 | + this.id = id; | |
52 | + } | |
53 | + | |
54 | + public String getKey() { | |
55 | + return key; | |
56 | + } | |
57 | + | |
58 | + public void setKey(String key) { | |
59 | + this.key = key; | |
60 | + } | |
61 | + | |
62 | + public String getName() { | |
63 | + return name; | |
64 | + } | |
65 | + | |
66 | + public void setName(String name) { | |
67 | + this.name = name; | |
68 | + } | |
69 | + | |
70 | + public Long getValue() { | |
71 | + return value; | |
72 | + } | |
73 | + | |
74 | + public void setValue(Long value) { | |
75 | + this.value = value; | |
76 | + } | |
77 | + | |
78 | + public Integer getStep() { | |
79 | + return step; | |
80 | + } | |
81 | + | |
82 | + public void setStep(Integer step) { | |
83 | + this.step = step; | |
84 | + } | |
85 | + | |
86 | + public String getPattern() { | |
87 | + return pattern; | |
88 | + } | |
89 | + | |
90 | + public void setPattern(String pattern) { | |
91 | + this.pattern = pattern; | |
92 | + } | |
93 | + | |
94 | + public LocalDate getExpiredOn() { | |
95 | + return expiredOn; | |
96 | + } | |
97 | + | |
98 | + public void setExpiredOn(LocalDate expiredOn) { | |
99 | + this.expiredOn = expiredOn; | |
100 | + } | |
101 | + | |
102 | + public LocalDate getToday() { | |
103 | + return today; | |
104 | + } | |
105 | + | |
106 | + public void setToday(LocalDate today) { | |
107 | + this.today = today; | |
108 | + } | |
109 | + | |
110 | + public Integer getVersion() { | |
111 | + return version; | |
112 | + } | |
113 | + | |
114 | + public void setVersion(Integer version) { | |
115 | + this.version = version; | |
116 | + } | |
117 | + | |
118 | + public static Builder builder() { | |
119 | + return new PersistentSequenceKey().new Builder(); | |
120 | + } | |
121 | + | |
122 | + public class Builder { | |
123 | + public Builder key(String key) { | |
124 | + PersistentSequenceKey.this.key = key; | |
125 | + return this; | |
126 | + } | |
127 | + | |
128 | + public Builder name(String name) { | |
129 | + PersistentSequenceKey.this.name = name; | |
130 | + return this; | |
131 | + } | |
132 | + | |
133 | + public Builder value(Long value) { | |
134 | + PersistentSequenceKey.this.value = value; | |
135 | + return this; | |
136 | + } | |
137 | + | |
138 | + public Builder step(Integer step) { | |
139 | + PersistentSequenceKey.this.step = step; | |
140 | + return this; | |
141 | + } | |
142 | + | |
143 | + public Builder pattern(String pattern) { | |
144 | + PersistentSequenceKey.this.pattern = pattern; | |
145 | + return this; | |
146 | + } | |
147 | + | |
148 | + public Builder expiredOn(LocalDate expiredOn) { | |
149 | + PersistentSequenceKey.this.expiredOn = expiredOn; | |
150 | + return this; | |
151 | + } | |
152 | + | |
153 | + public Builder version(Integer version) { | |
154 | + PersistentSequenceKey.this.version = version; | |
155 | + return this; | |
156 | + } | |
157 | + | |
158 | + public PersistentSequenceKey build() { | |
159 | + return PersistentSequenceKey.this; | |
160 | + } | |
161 | + } | |
162 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/domain/SequenceKey.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/domain/SequenceKey.java | |
1 | +package com.diligrp.boss.shared.domain; | |
2 | + | |
3 | +import java.time.LocalDate; | |
4 | + | |
5 | +public class SequenceKey { | |
6 | + private long sequence; | |
7 | + private LocalDate when; | |
8 | + | |
9 | + public SequenceKey(long sequence, LocalDate when) { | |
10 | + this.sequence = sequence; | |
11 | + this.when = when; | |
12 | + } | |
13 | + | |
14 | + public long getSequence() { | |
15 | + return sequence; | |
16 | + } | |
17 | + | |
18 | + public LocalDate getWhen() { | |
19 | + return when; | |
20 | + } | |
21 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/exception/DefaultExceptionHandler.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/exception/DefaultExceptionHandler.java | |
1 | +package com.diligrp.boss.shared.exception; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.Message; | |
4 | +import com.diligrp.boss.shared.ErrorCode; | |
5 | +import org.slf4j.Logger; | |
6 | +import org.slf4j.LoggerFactory; | |
7 | +import org.springframework.web.bind.annotation.ExceptionHandler; | |
8 | +import org.springframework.web.bind.annotation.RestControllerAdvice; | |
9 | + | |
10 | +@RestControllerAdvice | |
11 | +public class DefaultExceptionHandler { | |
12 | + private final Logger LOG = LoggerFactory.getLogger(this.getClass()); | |
13 | + | |
14 | + @ExceptionHandler(PlatformServiceException.class) | |
15 | + public Message<?> platformServiceException(PlatformServiceException ex) { | |
16 | + LOG.warn("assistant platform service exception", ex); | |
17 | + return Message.failure(ex.getCode(), ex.getMessage()); | |
18 | + } | |
19 | + | |
20 | + @ExceptionHandler(IllegalArgumentException.class) | |
21 | + public Message<?> illegalArgumentException(IllegalArgumentException ex) { | |
22 | + LOG.warn("assistant platform service exception", ex); | |
23 | + return Message.failure(ErrorCode.ILLEGAL_ARGUMENT_ERROR, ex.getMessage()); | |
24 | + } | |
25 | + | |
26 | + @ExceptionHandler(Exception.class) | |
27 | + public Message<?> defaultExceptionHandler(Exception ex) { | |
28 | + LOG.warn("assistant platform service exception", ex); | |
29 | + return Message.failure(ErrorCode.SYSTEM_UNKNOWN_ERROR, ErrorCode.MESSAGE_UNKNOWN_ERROR); | |
30 | + } | |
31 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/exception/PlatformServiceException.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/exception/PlatformServiceException.java | |
1 | +package com.diligrp.boss.shared.exception; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | + | |
5 | +/** | |
6 | + * 所有模块异常类的基类 | |
7 | + */ | |
8 | +public class PlatformServiceException extends RuntimeException { | |
9 | + /** | |
10 | + * 错误码 | |
11 | + */ | |
12 | + private int code = ErrorCode.SYSTEM_UNKNOWN_ERROR; | |
13 | + | |
14 | + /** | |
15 | + * 是否打印异常栈 | |
16 | + */ | |
17 | + private boolean stackTrace = true; | |
18 | + | |
19 | + public PlatformServiceException(String message) { | |
20 | + super(message); | |
21 | + } | |
22 | + | |
23 | + public PlatformServiceException(int code, String message) { | |
24 | + super(message); | |
25 | + this.code = code; | |
26 | + this.stackTrace = false; | |
27 | + } | |
28 | + | |
29 | + public PlatformServiceException(String message, Throwable ex) { | |
30 | + super(message, ex); | |
31 | + } | |
32 | + | |
33 | + @Override | |
34 | + public Throwable fillInStackTrace() { | |
35 | + return stackTrace ? super.fillInStackTrace() : this; | |
36 | + } | |
37 | + | |
38 | + public int getCode() { | |
39 | + return code; | |
40 | + } | |
41 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceAccessException.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceAccessException.java | |
1 | +package com.diligrp.boss.shared.exception; | |
2 | + | |
3 | +/** | |
4 | + * 远程服务访问异常 | |
5 | + */ | |
6 | +public class ServiceAccessException extends PlatformServiceException { | |
7 | + public ServiceAccessException(String message) { | |
8 | + super(message); | |
9 | + } | |
10 | + | |
11 | + public ServiceAccessException(int code, String message) { | |
12 | + super(code, message); | |
13 | + } | |
14 | + | |
15 | + public ServiceAccessException(String message, Throwable ex) { | |
16 | + super(message, ex); | |
17 | + } | |
18 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceConnectException.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceConnectException.java | |
1 | +package com.diligrp.boss.shared.exception; | |
2 | + | |
3 | +/** | |
4 | + * 远程服务连接异常 | |
5 | + */ | |
6 | +public class ServiceConnectException extends ServiceAccessException { | |
7 | + | |
8 | + public ServiceConnectException(String message) { | |
9 | + super(message); | |
10 | + } | |
11 | + | |
12 | + public ServiceConnectException(int code, String message) { | |
13 | + super(code, message); | |
14 | + } | |
15 | + | |
16 | + public ServiceConnectException(String message, Throwable ex) { | |
17 | + super(message, ex); | |
18 | + } | |
19 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceTimeoutException.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/exception/ServiceTimeoutException.java | |
1 | +package com.diligrp.boss.shared.exception; | |
2 | + | |
3 | +/** | |
4 | + * 远程服务访问超时异常 | |
5 | + */ | |
6 | +public class ServiceTimeoutException extends ServiceAccessException { | |
7 | + | |
8 | + public ServiceTimeoutException(String message) { | |
9 | + super(message); | |
10 | + } | |
11 | + | |
12 | + public ServiceTimeoutException(int code, String message) { | |
13 | + super(code, message); | |
14 | + } | |
15 | + | |
16 | + public ServiceTimeoutException(String message, Throwable ex) { | |
17 | + super(message, ex); | |
18 | + } | |
19 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/mybatis/GenericEnumTypeHandler.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/mybatis/GenericEnumTypeHandler.java | |
1 | +package com.diligrp.boss.shared.mybatis; | |
2 | + | |
3 | +import com.diligrp.boss.shared.type.IEnumType; | |
4 | +import org.apache.ibatis.type.BaseTypeHandler; | |
5 | +import org.apache.ibatis.type.JdbcType; | |
6 | + | |
7 | +import java.sql.CallableStatement; | |
8 | +import java.sql.PreparedStatement; | |
9 | +import java.sql.ResultSet; | |
10 | +import java.sql.SQLException; | |
11 | +import java.util.Arrays; | |
12 | + | |
13 | +public class GenericEnumTypeHandler<E extends IEnumType> extends BaseTypeHandler<E> { | |
14 | + private final E[] enums; | |
15 | + | |
16 | + public GenericEnumTypeHandler(Class<E> type) { | |
17 | + if (type == null) { | |
18 | + throw new IllegalArgumentException("Type argument cannot be null"); | |
19 | + } else { | |
20 | + this.enums = type.getEnumConstants(); | |
21 | + if (this.enums == null) { | |
22 | + throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type."); | |
23 | + } | |
24 | + } | |
25 | + } | |
26 | + | |
27 | + @Override | |
28 | + public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException { | |
29 | + preparedStatement.setInt(i, e.getCode()); | |
30 | + } | |
31 | + | |
32 | + @Override | |
33 | + public E getNullableResult(ResultSet resultSet, String columnName) throws SQLException { | |
34 | + int code = resultSet.getInt(columnName); | |
35 | + if (resultSet.wasNull()) { | |
36 | + return null; | |
37 | + } else { | |
38 | + return getEnumType(code); | |
39 | + } | |
40 | + } | |
41 | + | |
42 | + @Override | |
43 | + public E getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { | |
44 | + int code = resultSet.getInt(columnIndex); | |
45 | + if (resultSet.wasNull()) { | |
46 | + return null; | |
47 | + } else { | |
48 | + return getEnumType(code); | |
49 | + } | |
50 | + } | |
51 | + | |
52 | + @Override | |
53 | + public E getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { | |
54 | + int code = callableStatement.getInt(columnIndex); | |
55 | + if (callableStatement.wasNull()) { | |
56 | + return null; | |
57 | + } else { | |
58 | + return getEnumType(code); | |
59 | + } | |
60 | + } | |
61 | + | |
62 | + private E getEnumType(int code) { | |
63 | + return Arrays.stream(enums).filter(item -> item.getCode() == code).findFirst().orElse(null); | |
64 | + } | |
65 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/mybatis/MybatisMapperSupport.java
0 → 100644
boss-shared/src/main/java/com/diligrp/boss/shared/security/AesCipher.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/AesCipher.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import javax.crypto.Cipher; | |
4 | +import javax.crypto.KeyGenerator; | |
5 | +import javax.crypto.SecretKey; | |
6 | +import javax.crypto.spec.SecretKeySpec; | |
7 | +import java.security.Key; | |
8 | +import java.util.Base64; | |
9 | + | |
10 | +/** | |
11 | + * AES算法工具类 | |
12 | + */ | |
13 | +public class AesCipher { | |
14 | + private static final String KEY_ALGORITHM = "AES"; | |
15 | + | |
16 | + private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; | |
17 | + | |
18 | + public static String generateSecretKey() throws Exception { | |
19 | + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); | |
20 | + keyGenerator.init(128); | |
21 | + | |
22 | + SecretKey secretKey = keyGenerator.generateKey(); | |
23 | + return Base64.getEncoder().encodeToString(secretKey.getEncoded()); | |
24 | + } | |
25 | + | |
26 | + public static byte[] encrypt(byte[] data, String secretKey) throws Exception { | |
27 | + Key key = toKey(secretKey); | |
28 | + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | |
29 | + cipher.init(Cipher.ENCRYPT_MODE, key); | |
30 | + | |
31 | + return cipher.doFinal(data); | |
32 | + } | |
33 | + | |
34 | + public static byte[] decrypt(byte[] data, String secretKey) throws Exception { | |
35 | + Key key = toKey(secretKey); | |
36 | + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | |
37 | + cipher.init(Cipher.DECRYPT_MODE, key); | |
38 | + | |
39 | + return cipher.doFinal(data); | |
40 | + } | |
41 | + | |
42 | + private static Key toKey(String secretKey) { | |
43 | + byte[] key = Base64.getDecoder().decode(secretKey); | |
44 | + return new SecretKeySpec(key, KEY_ALGORITHM); | |
45 | + } | |
46 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/HexUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/HexUtils.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +/** | |
4 | + * 字节数组与十六进制字符串转化工具类 | |
5 | + */ | |
6 | +public class HexUtils { | |
7 | + | |
8 | + private static final int GUARD_CHAR = 0x01; | |
9 | + | |
10 | + private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', | |
11 | + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; | |
12 | + | |
13 | + private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', | |
14 | + '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; | |
15 | + | |
16 | + public static char[] encodeHex(byte[] data) { | |
17 | + return encodeHex(data, true); | |
18 | + } | |
19 | + | |
20 | + public static char[] encodeHex(byte[] data, boolean toLowerCase) { | |
21 | + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); | |
22 | + } | |
23 | + | |
24 | + public static String encodeHexStr(byte[] data) { | |
25 | + return encodeHexStr(data, true); | |
26 | + } | |
27 | + | |
28 | + public static String encodeHexStr(byte[] data, boolean toLowerCase) { | |
29 | + return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); | |
30 | + } | |
31 | + | |
32 | + public static byte[] decodeHex(String data) { | |
33 | + return decodeHex(data.toCharArray()); | |
34 | + } | |
35 | + | |
36 | + public static byte[] decodeHex(char[] data) { | |
37 | + int len = data.length; | |
38 | + if ((len & GUARD_CHAR) != 0) { | |
39 | + throw new RuntimeException("Unknown char"); | |
40 | + } | |
41 | + | |
42 | + byte[] out = new byte[len >> 1]; | |
43 | + for (int i = 0, j = 0; j < len; i++) { | |
44 | + int f = toDigit(data[j], j) << 4; | |
45 | + j++; | |
46 | + f = f | toDigit(data[j], j); | |
47 | + j++; | |
48 | + out[i] = (byte) (f & 0xFF); | |
49 | + } | |
50 | + | |
51 | + return out; | |
52 | + } | |
53 | + | |
54 | + private static char[] encodeHex(byte[] data, char[] toDigits) { | |
55 | + int l = data.length; | |
56 | + char[] out = new char[l << 1]; | |
57 | + for (int i = 0, j = 0; i < l; i++) { | |
58 | + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; | |
59 | + out[j++] = toDigits[0x0F & data[i]]; | |
60 | + } | |
61 | + return out; | |
62 | + } | |
63 | + | |
64 | + private static String encodeHexStr(byte[] data, char[] toDigits) { | |
65 | + return new String(encodeHex(data, toDigits)); | |
66 | + } | |
67 | + | |
68 | + private static int toDigit(char ch, int index) { | |
69 | + int digit = Character.digit(ch, 16); | |
70 | + if (digit == -1) { | |
71 | + throw new RuntimeException("Invalid hex char " + ch + ", index at " + index); | |
72 | + } | |
73 | + return digit; | |
74 | + } | |
75 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/KeyStoreUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/KeyStoreUtils.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import com.diligrp.boss.shared.util.ClassUtils; | |
4 | + | |
5 | +import java.io.InputStream; | |
6 | +import java.security.KeyStore; | |
7 | +import java.security.PrivateKey; | |
8 | +import java.security.PublicKey; | |
9 | +import java.security.cert.Certificate; | |
10 | +import java.security.cert.CertificateFactory; | |
11 | +import java.util.Base64; | |
12 | + | |
13 | +/** | |
14 | + * 数字证书工具类 | |
15 | + */ | |
16 | +public class KeyStoreUtils { | |
17 | + public static String getPrivateKeyStr(String keyStorePath, String storeType, String storePass, | |
18 | + String alias, String keyPass) throws Exception { | |
19 | + InputStream in = ClassUtils.getDefaultClassLoader().getResourceAsStream(keyStorePath); | |
20 | + PrivateKey privateKey = getPrivateKey(in, storeType, storePass, alias, keyPass); | |
21 | + return Base64.getEncoder().encodeToString(privateKey.getEncoded()); | |
22 | + } | |
23 | + | |
24 | + public static PrivateKey getPrivateKey(String keyStorePath, String storeType, String storePass, | |
25 | + String alias, String keyPass) throws Exception { | |
26 | + InputStream in = ClassUtils.getDefaultClassLoader().getResourceAsStream(keyStorePath); | |
27 | + return getPrivateKey(in, storeType, storePass, alias, keyPass); | |
28 | + } | |
29 | + | |
30 | + public static PrivateKey getPrivateKey(InputStream in, String storeType, String storePass, String alias, String keyPass) throws Exception { | |
31 | + KeyStore ks = getKeyStore(in, storeType, storePass); | |
32 | + PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray()); | |
33 | + return key; | |
34 | + } | |
35 | + | |
36 | + public static String getPublicKeyStr(String keyStorePath, String storeType, String storePass, String alias) throws Exception { | |
37 | + InputStream in = ClassUtils.getDefaultClassLoader().getResourceAsStream(keyStorePath); | |
38 | + PublicKey publicKey = getPublicKey(in, storeType, storePass, alias); | |
39 | + return Base64.getEncoder().encodeToString(publicKey.getEncoded()); | |
40 | + } | |
41 | + | |
42 | + public static PublicKey getPublicKey(String keyStorePath, String storeType, String storePass, String alias) throws Exception { | |
43 | + InputStream in = ClassUtils.getDefaultClassLoader().getResourceAsStream(keyStorePath); | |
44 | + return getPublicKey(in, storeType, storePass, alias); | |
45 | + } | |
46 | + | |
47 | + public static PublicKey getPublicKey(InputStream in, String storeType, String storePass, String alias) throws Exception { | |
48 | + KeyStore ks = getKeyStore(in, storeType, storePass); | |
49 | + Certificate cert = ks.getCertificate(alias); | |
50 | + return cert.getPublicKey(); | |
51 | + } | |
52 | + | |
53 | + public static String getPublicKeyStr(String certificatePath) throws Exception { | |
54 | + PublicKey publicKey = getPublicKey(certificatePath); | |
55 | + return Base64.getEncoder().encodeToString(publicKey.getEncoded()); | |
56 | + } | |
57 | + | |
58 | + public static PublicKey getPublicKey(String certificatePath) throws Exception { | |
59 | + InputStream in = ClassUtils.getDefaultClassLoader().getResourceAsStream(certificatePath); | |
60 | + Certificate x509Cert = CertificateFactory.getInstance("X509").generateCertificate(in); | |
61 | + return x509Cert.getPublicKey(); | |
62 | + } | |
63 | + | |
64 | + public static KeyStore getKeyStore(InputStream in, String storeType, String storePass) throws Exception { | |
65 | + KeyStore ks = KeyStore.getInstance(storeType); | |
66 | + ks.load(in, storePass.toCharArray()); | |
67 | + in.close(); | |
68 | + return ks; | |
69 | + } | |
70 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/Md5Cipher.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/Md5Cipher.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import java.security.MessageDigest; | |
4 | + | |
5 | +/** | |
6 | + * MD5算法工具类 | |
7 | + */ | |
8 | +public class Md5Cipher { | |
9 | + private static final String KEY_MD5 = "MD5"; | |
10 | + | |
11 | + public static byte[] encrypt(byte[] data) throws Exception { | |
12 | + MessageDigest md5 = MessageDigest.getInstance(KEY_MD5); | |
13 | + md5.update(data); | |
14 | + return md5.digest(); | |
15 | + } | |
16 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/PasswordUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/PasswordUtils.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import java.nio.charset.StandardCharsets; | |
4 | + | |
5 | +/** | |
6 | + * 密码加密散列工具类 | |
7 | + */ | |
8 | +public class PasswordUtils { | |
9 | + public static String generateSecretKey() { | |
10 | + try { | |
11 | + return AesCipher.generateSecretKey(); | |
12 | + } catch (Exception ex) { | |
13 | + throw new RuntimeException("Generate password secret key error", ex); | |
14 | + } | |
15 | + } | |
16 | + | |
17 | + public static String encrypt(String password, String secretKey) { | |
18 | + try { | |
19 | + byte[] data = password.getBytes(StandardCharsets.UTF_8); | |
20 | + return HexUtils.encodeHexStr(ShaCipher.encrypt(AesCipher.encrypt(data, secretKey))); | |
21 | + } catch (Exception ex) { | |
22 | + throw new RuntimeException("Encrypt password error", ex); | |
23 | + } | |
24 | + } | |
25 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/PbeCipher.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/PbeCipher.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import javax.crypto.Cipher; | |
4 | +import javax.crypto.SecretKey; | |
5 | +import javax.crypto.SecretKeyFactory; | |
6 | +import javax.crypto.spec.PBEKeySpec; | |
7 | +import javax.crypto.spec.PBEParameterSpec; | |
8 | +import java.security.Key; | |
9 | +import java.util.Random; | |
10 | + | |
11 | +/** | |
12 | + * PBE算法工具类 | |
13 | + */ | |
14 | +public class PbeCipher { | |
15 | + private static final String ALGORITHM = "PBEWithSHA1AndDESede"; | |
16 | + | |
17 | + public static byte[] encrypt(byte[] data, String password, byte[] salt) throws Exception { | |
18 | + Key key = toKey(password); | |
19 | + | |
20 | + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100); | |
21 | + Cipher cipher = Cipher.getInstance(ALGORITHM); | |
22 | + cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec); | |
23 | + | |
24 | + return cipher.doFinal(data); | |
25 | + } | |
26 | + | |
27 | + public static byte[] decrypt(byte[] data, String password, byte[] salt) throws Exception { | |
28 | + Key key = toKey(password); | |
29 | + | |
30 | + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 100); | |
31 | + Cipher cipher = Cipher.getInstance(ALGORITHM); | |
32 | + cipher.init(Cipher.DECRYPT_MODE, key, paramSpec); | |
33 | + | |
34 | + return cipher.doFinal(data); | |
35 | + } | |
36 | + | |
37 | + private static byte[] initSalt() throws Exception { | |
38 | + byte[] salt = new byte[8]; | |
39 | + Random random = new Random(); | |
40 | + random.nextBytes(salt); | |
41 | + return salt; | |
42 | + } | |
43 | + | |
44 | + private static Key toKey(String password) throws Exception { | |
45 | + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); | |
46 | + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM); | |
47 | + SecretKey secretKey = keyFactory.generateSecret(keySpec); | |
48 | + | |
49 | + return secretKey; | |
50 | + } | |
51 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/RsaCipher.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/RsaCipher.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import javax.crypto.Cipher; | |
4 | +import java.security.*; | |
5 | +import java.security.spec.PKCS8EncodedKeySpec; | |
6 | +import java.security.spec.X509EncodedKeySpec; | |
7 | +import java.util.Base64; | |
8 | + | |
9 | +/** | |
10 | + * RSA算法工具类 | |
11 | + */ | |
12 | +public class RsaCipher { | |
13 | + private static final String KEY_ALGORITHM = "RSA"; | |
14 | + | |
15 | + private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; | |
16 | + | |
17 | + public static String[] generateRSAKeyPair() throws Exception { | |
18 | + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); | |
19 | + keyPairGen.initialize(512, new SecureRandom()); | |
20 | + KeyPair keyPair = keyPairGen.generateKeyPair(); | |
21 | + PrivateKey privateKey = keyPair.getPrivate(); | |
22 | + PublicKey publicKey = keyPair.getPublic(); | |
23 | + | |
24 | + String[] keyPairArray = new String[2]; | |
25 | + keyPairArray[0] = Base64.getEncoder().encodeToString(privateKey.getEncoded()); | |
26 | + keyPairArray[1] = Base64.getEncoder().encodeToString(publicKey.getEncoded()); | |
27 | + return keyPairArray; | |
28 | + } | |
29 | + | |
30 | + public static byte[] encrypt(byte[] data, Key secretKey) throws Exception { | |
31 | + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); | |
32 | + cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |
33 | + return cipher.doFinal(data); | |
34 | + } | |
35 | + | |
36 | + public static byte[] decrypt(byte[] data, Key secretKey) throws Exception { | |
37 | + Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); | |
38 | + cipher.init(Cipher.DECRYPT_MODE, secretKey); | |
39 | + return cipher.doFinal(data); | |
40 | + } | |
41 | + | |
42 | + public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception { | |
43 | + Signature signature = Signature.getInstance(SIGN_ALGORITHMS); | |
44 | + signature.initSign(privateKey, new SecureRandom()); | |
45 | + signature.update(data); | |
46 | + return signature.sign(); | |
47 | + } | |
48 | + | |
49 | + public static boolean verify(byte[] data, byte[] sign, PublicKey publicKey) throws Exception { | |
50 | + Signature signature = Signature.getInstance(SIGN_ALGORITHMS); | |
51 | + signature.initVerify(publicKey); | |
52 | + signature.update(data); | |
53 | + return signature.verify(sign); | |
54 | + } | |
55 | + | |
56 | + public static PrivateKey getPrivateKey(String key) throws Exception { | |
57 | + byte[] keyBytes = Base64.getDecoder().decode(key); | |
58 | + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); | |
59 | + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
60 | + return keyFactory.generatePrivate(keySpec); | |
61 | + } | |
62 | + | |
63 | + public static PublicKey getPublicKey(String key) throws Exception { | |
64 | + byte[] keyBytes = Base64.getDecoder().decode(key); | |
65 | + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); | |
66 | + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); | |
67 | + return keyFactory.generatePublic(keySpec); | |
68 | + } | |
69 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/security/ShaCipher.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/security/ShaCipher.java | |
1 | +package com.diligrp.boss.shared.security; | |
2 | + | |
3 | +import java.security.MessageDigest; | |
4 | + | |
5 | +/** | |
6 | + * SHA散列算法工具类 | |
7 | + */ | |
8 | +public class ShaCipher { | |
9 | + private static final String KEY_SHA = "SHA"; | |
10 | + | |
11 | + public static byte[] encrypt(byte[] data) throws Exception { | |
12 | + MessageDigest sha = MessageDigest.getInstance(KEY_SHA); | |
13 | + sha.update(data); | |
14 | + | |
15 | + return sha.digest(); | |
16 | + } | |
17 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/service/SequenceKeyService.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/service/SequenceKeyService.java | |
1 | +package com.diligrp.boss.shared.service; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.PersistentSequenceKey; | |
4 | + | |
5 | +/** | |
6 | + * SequenceKey数据同步基础类 | |
7 | + * | |
8 | + * @author: brenthuang | |
9 | + * @date: 2020/03/24 | |
10 | + */ | |
11 | +public interface SequenceKeyService { | |
12 | + /** | |
13 | + * 注册SequenceKey | |
14 | + */ | |
15 | + void registerSequenceKey(PersistentSequenceKey sequenceKey); | |
16 | + | |
17 | + /** | |
18 | + * 查找指定的SequenceKey | |
19 | + * | |
20 | + * @param key - SequenceKey的唯一标识 | |
21 | + * @return SequenceKey | |
22 | + */ | |
23 | + PersistentSequenceKey findSequenceKey(String key); | |
24 | + | |
25 | + /** | |
26 | + * 根据KeyId查询SequenceKey | |
27 | + * | |
28 | + * @param id - KeyId | |
29 | + * @return SequenceKey | |
30 | + */ | |
31 | + PersistentSequenceKey findSequenceKeyById(Long id); | |
32 | + | |
33 | + /** | |
34 | + * 通过悲观锁实现同步从数据库获取基于过期日期的SequenceKey | |
35 | + * | |
36 | + * 根据数据库主键锁定数据记录(加行锁),根据SequenceKey的过期日期更新下一个startWith值 | |
37 | + * 当SequenceKey过期时value将重新设置为1,否则value + 1 | |
38 | + * | |
39 | + * @param id - KeyId | |
40 | + * @return SequenceKey | |
41 | + */ | |
42 | + PersistentSequenceKey synchronizeSequenceKey(Long id); | |
43 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/service/SequenceKeyServiceImpl.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/service/SequenceKeyServiceImpl.java | |
1 | +package com.diligrp.boss.shared.service; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.dao.SequenceKeyDao; | |
5 | +import com.diligrp.boss.shared.domain.PersistentSequenceKey; | |
6 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
7 | +import org.springframework.stereotype.Service; | |
8 | +import org.springframework.transaction.annotation.Transactional; | |
9 | + | |
10 | +import java.time.LocalDate; | |
11 | + | |
12 | +/** | |
13 | + * SequenceKey数据同步的实现类 | |
14 | + */ | |
15 | +@Service("sequenceKeyService") | |
16 | +public class SequenceKeyServiceImpl implements SequenceKeyService { | |
17 | + | |
18 | + private final SequenceKeyDao sequenceKeyDao; | |
19 | + | |
20 | + public SequenceKeyServiceImpl(SequenceKeyDao sequenceKeyDao) { | |
21 | + this.sequenceKeyDao = sequenceKeyDao; | |
22 | + } | |
23 | + | |
24 | + @Override | |
25 | + @Transactional(rollbackFor = Exception.class) | |
26 | + public void registerSequenceKey(PersistentSequenceKey sequenceKey) { | |
27 | + sequenceKeyDao.insertSequenceKey(sequenceKey); | |
28 | + } | |
29 | + | |
30 | + @Override | |
31 | + public PersistentSequenceKey findSequenceKey(String key) { | |
32 | + return sequenceKeyDao.findSequenceKey(key).orElseThrow(() -> | |
33 | + new PlatformServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | |
34 | + } | |
35 | + | |
36 | + @Override | |
37 | + public PersistentSequenceKey findSequenceKeyById(Long id) { | |
38 | + return sequenceKeyDao.findSequenceKeyById(id).orElseThrow(() -> | |
39 | + new PlatformServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | |
40 | + } | |
41 | + | |
42 | + /** | |
43 | + * 数据库的行锁只有在事务提交之后才会释放,这里使用业务层的Spring事务,因此行锁将不能很快释放,这样势必会降低此代码块的并发性能。 | |
44 | + * 如果新开一个Spring事务Propagation.REQUIRES_NEW,与业务层事务独立将无法保证ID的连续性(不能随着业务层的失败回滚生成的ID) | |
45 | + */ | |
46 | + @Override | |
47 | +// @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) | |
48 | + @Transactional(rollbackFor = Exception.class) | |
49 | + public PersistentSequenceKey synchronizeSequenceKey(Long id) { | |
50 | + // 悲观锁添加行锁 - 多JVM多线程场景下自动实现线程同步 | |
51 | + // 通过SELECT FOR UPDATE锁定了行,当事务提交时将自动释放行锁 | |
52 | + PersistentSequenceKey persistentKey = sequenceKeyDao.lockSequenceKey(id).orElseThrow(() -> | |
53 | + new PlatformServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | |
54 | + | |
55 | + // 当PersistentKey设置了过期时间并且已经过期时, 则value重新设置成1并刷新过期日期为今天,否则设置value+=step | |
56 | + LocalDate today = persistentKey.getToday(); | |
57 | + LocalDate expiredDay = persistentKey.getExpiredOn(); | |
58 | + if (expiredDay != null && today.isAfter(persistentKey.getExpiredOn())) { | |
59 | + persistentKey.setValue(1L); | |
60 | + expiredDay = today; | |
61 | + } | |
62 | + | |
63 | + PersistentSequenceKey newKey = new PersistentSequenceKey(); | |
64 | + newKey.setId(persistentKey.getId()); | |
65 | + newKey.setValue(persistentKey.getValue() + persistentKey.getStep()); | |
66 | + newKey.setExpiredOn(expiredDay); | |
67 | + | |
68 | + sequenceKeyDao.unlockSequenceKey(newKey); | |
69 | + | |
70 | + return persistentKey; | |
71 | + } | |
72 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/service/ServiceEndpointSupport.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/service/ServiceEndpointSupport.java | |
1 | +package com.diligrp.boss.shared.service; | |
2 | + | |
3 | +import com.diligrp.boss.shared.exception.ServiceAccessException; | |
4 | +import com.diligrp.boss.shared.exception.ServiceConnectException; | |
5 | +import com.diligrp.boss.shared.exception.ServiceTimeoutException; | |
6 | +import com.diligrp.boss.shared.util.ObjectUtils; | |
7 | + | |
8 | +import javax.net.ssl.SSLContext; | |
9 | +import javax.net.ssl.SSLParameters; | |
10 | +import java.net.ConnectException; | |
11 | +import java.net.URI; | |
12 | +import java.net.URLEncoder; | |
13 | +import java.net.http.*; | |
14 | +import java.nio.charset.StandardCharsets; | |
15 | +import java.time.Duration; | |
16 | +import java.util.Arrays; | |
17 | +import java.util.List; | |
18 | +import java.util.Map; | |
19 | +import java.util.Optional; | |
20 | +import java.util.concurrent.Executor; | |
21 | + | |
22 | +public abstract class ServiceEndpointSupport { | |
23 | + protected static final int MAX_CONNECT_TIMEOUT_TIME = 10000; | |
24 | + | |
25 | + protected static final int MAX_REQUEST_TIMEOUT_TIME = 30000; | |
26 | + | |
27 | + protected static final String CONTENT_TYPE = "Content-Type"; | |
28 | + | |
29 | + protected static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8"; | |
30 | + | |
31 | + protected static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded;charset=UTF-8"; | |
32 | + | |
33 | + protected static final String CONTENT_TYPE_XML = "text/xml;charset=UTF-8"; | |
34 | + | |
35 | + protected volatile HttpClient httpClient; | |
36 | + | |
37 | + protected Object lock = new Object(); | |
38 | + | |
39 | + /** | |
40 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
41 | + */ | |
42 | + public HttpResult send(String requestUrl, String body) { | |
43 | + return send(requestUrl, null, body); | |
44 | + } | |
45 | + | |
46 | + /** | |
47 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
48 | + */ | |
49 | + public HttpResult send(String requestUrl, HttpHeader[] headers, String body) { | |
50 | + if (ObjectUtils.isEmpty(requestUrl)) { | |
51 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | |
52 | + } | |
53 | + | |
54 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | |
55 | + .version(HttpClient.Version.HTTP_2) | |
56 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | |
57 | + .header(CONTENT_TYPE, CONTENT_TYPE_JSON) | |
58 | + .POST(HttpRequest.BodyPublishers.ofString(body)); | |
59 | + // Wrap the HTTP headers | |
60 | + if (headers != null && headers.length > 0) { | |
61 | + // request.headers(Arrays.stream(headers).flatMap(h -> Stream.of(h.param, h.value)).toArray(String[]::new)); | |
62 | + Arrays.stream(headers).filter(h -> h != null).forEach(h -> request.header(h.param, h.value)); | |
63 | + } | |
64 | + | |
65 | + return execute(request.build()); | |
66 | + } | |
67 | + | |
68 | + /** | |
69 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
70 | + */ | |
71 | + public HttpResult send(String requestUrl, HttpParam[] params) { | |
72 | + return send(requestUrl, null, params); | |
73 | + } | |
74 | + | |
75 | + /** | |
76 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
77 | + */ | |
78 | + public HttpResult send(String requestUrl, HttpHeader[] headers, HttpParam[] params) { | |
79 | + if (ObjectUtils.isEmpty(requestUrl)) { | |
80 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | |
81 | + } | |
82 | + | |
83 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | |
84 | + .version(HttpClient.Version.HTTP_2) | |
85 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | |
86 | + .header(CONTENT_TYPE, CONTENT_TYPE_FORM); | |
87 | + // Wrap the HTTP headers | |
88 | + if (headers != null && headers.length > 0) { | |
89 | + Arrays.stream(headers).filter(h -> h != null).forEach(h -> request.header(h.param, h.value)); | |
90 | + } | |
91 | + if (params != null && params.length > 0) { | |
92 | + // [key1, value1, key2, value2] -> key1=value1&key2=value2 | |
93 | + String body = Arrays.stream(params).filter(p -> p != null) | |
94 | + .map(p -> "".concat(p.param).concat("=").concat(URLEncoder.encode(p.value, StandardCharsets.UTF_8))) | |
95 | + .reduce((value1, value2) -> "".concat(value1).concat("&").concat(value2)).get(); | |
96 | + request.POST(HttpRequest.BodyPublishers.ofString(body)); | |
97 | + } | |
98 | + | |
99 | + return execute(request.build()); | |
100 | + } | |
101 | + | |
102 | + /** | |
103 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
104 | + */ | |
105 | + public HttpResult sendXml(String requestUrl, HttpHeader[] headers, String xml) { | |
106 | + if (ObjectUtils.isEmpty(requestUrl)) { | |
107 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | |
108 | + } | |
109 | + | |
110 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | |
111 | + .version(HttpClient.Version.HTTP_2) | |
112 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | |
113 | + .header(CONTENT_TYPE, CONTENT_TYPE_XML) | |
114 | + .POST(HttpRequest.BodyPublishers.ofString(xml)); | |
115 | + // Wrap the HTTP headers | |
116 | + if (headers != null && headers.length > 0) { | |
117 | + Arrays.stream(headers).filter(h -> h != null).forEach(h -> request.header(h.param, h.value)); | |
118 | + } | |
119 | + | |
120 | + return execute(request.build()); | |
121 | + } | |
122 | + | |
123 | + protected HttpClient getHttpClient() { | |
124 | + if (httpClient == null) { | |
125 | + synchronized (lock) { | |
126 | + // Double check for performance purpose | |
127 | + if (httpClient == null) { | |
128 | + HttpClient.Builder builder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2) | |
129 | + // 认证,默认情况下Authenticator.getDefault()是null值,会报错 | |
130 | +// .authenticator(Authenticator.getDefault()) | |
131 | + // 缓存,默认情况下 CookieHandler.getDefault()是null值,会报错 | |
132 | +// .cookieHandler(CookieHandler.getDefault()) | |
133 | + .connectTimeout(Duration.ofMillis(MAX_CONNECT_TIMEOUT_TIME)) | |
134 | + .followRedirects(HttpClient.Redirect.NEVER); | |
135 | + // Build SSL | |
136 | + Optional<SSLContext> sslContext = buildSSLContext(); | |
137 | + sslContext.ifPresent(builder::sslContext); | |
138 | + Optional<SSLParameters> parameters = buildSslParameters(); | |
139 | + parameters.ifPresent(builder::sslParameters); | |
140 | + // Build thread pool | |
141 | + Optional<Executor> executor = buildThreadPool(); | |
142 | + executor.ifPresent(builder::executor); | |
143 | + httpClient = builder.build(); | |
144 | + } | |
145 | + } | |
146 | + } | |
147 | + | |
148 | + return httpClient; | |
149 | + } | |
150 | + | |
151 | + protected Optional<SSLContext> buildSSLContext() { | |
152 | + return Optional.ofNullable(null); | |
153 | + } | |
154 | + | |
155 | + protected Optional<SSLParameters> buildSslParameters() { | |
156 | + return Optional.ofNullable(null); | |
157 | + } | |
158 | + | |
159 | + protected Optional<Executor> buildThreadPool() { | |
160 | + return Optional.ofNullable(null); | |
161 | + } | |
162 | + | |
163 | + /** | |
164 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | |
165 | + */ | |
166 | + protected HttpResult execute(HttpRequest request) { | |
167 | + try { | |
168 | + HttpResponse<String> response = getHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); | |
169 | + | |
170 | + HttpResult result = HttpResult.create(); | |
171 | + result.statusCode = response.statusCode(); | |
172 | + result.responseText = response.body(); | |
173 | + result.headers = response.headers() == null ? null : response.headers().map(); | |
174 | + return result; | |
175 | + } catch (ConnectException | HttpConnectTimeoutException cex) { | |
176 | + throw new ServiceConnectException("Remote service connection exception", cex); | |
177 | + } catch (HttpTimeoutException hex) { | |
178 | + throw new ServiceTimeoutException("Remote service access timeout", hex); | |
179 | + } catch (Exception ex) { | |
180 | + throw new ServiceAccessException("Remote service access exception", ex); | |
181 | + } | |
182 | + } | |
183 | + | |
184 | + public static class HttpParam { | |
185 | + public String param; | |
186 | + public String value; | |
187 | + | |
188 | + private HttpParam(String param, String value) { | |
189 | + this.param = param; | |
190 | + this.value = value; | |
191 | + } | |
192 | + | |
193 | + public static HttpParam create(String param, String value) { | |
194 | + return new HttpParam(param, value); | |
195 | + } | |
196 | + } | |
197 | + | |
198 | + public static class HttpHeader { | |
199 | + public String param; | |
200 | + public String value; | |
201 | + | |
202 | + private HttpHeader(String param, String value) { | |
203 | + this.param = param; | |
204 | + this.value = value; | |
205 | + } | |
206 | + | |
207 | + public static HttpHeader create(String param, String value) { | |
208 | + return new HttpHeader(param, value); | |
209 | + } | |
210 | + } | |
211 | + | |
212 | + public static class HttpResult { | |
213 | + public int statusCode = -1; | |
214 | + public String responseText; | |
215 | + public Map<String, List<String>> headers; | |
216 | + | |
217 | + public static HttpResult create() { | |
218 | + return new HttpResult(); | |
219 | + } | |
220 | + | |
221 | + public String header(String key) { | |
222 | + if (headers == null) { | |
223 | + return null; | |
224 | + } | |
225 | + | |
226 | + List<String> values = headers.get(key); | |
227 | + if (values == null || values.isEmpty()) { | |
228 | + return null; | |
229 | + } | |
230 | + | |
231 | + return values.get(0); | |
232 | + } | |
233 | + } | |
234 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/service/ThreadPollService.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/service/ThreadPollService.java | |
1 | +package com.diligrp.boss.shared.service; | |
2 | + | |
3 | +import com.diligrp.boss.shared.Constants; | |
4 | + | |
5 | +import java.util.concurrent.ExecutorService; | |
6 | +import java.util.concurrent.LinkedBlockingQueue; | |
7 | +import java.util.concurrent.ThreadPoolExecutor; | |
8 | +import java.util.concurrent.TimeUnit; | |
9 | + | |
10 | +public final class ThreadPollService { | |
11 | + | |
12 | + private static volatile ExecutorService threadPoll; | |
13 | + | |
14 | + private ThreadPollService() { | |
15 | + } | |
16 | + | |
17 | + public static ExecutorService getInstance() { | |
18 | + if (threadPoll == null) { | |
19 | + synchronized (ThreadPollService.class) { | |
20 | + if (threadPoll == null) { | |
21 | + threadPoll = new ThreadPoolExecutor(Constants.CORE_POOL_SIZE, Constants.MAX_POOL_SIZE, | |
22 | + 1, TimeUnit.MINUTES, new LinkedBlockingQueue(1000), | |
23 | + new ThreadPoolExecutor.CallerRunsPolicy()); | |
24 | + } | |
25 | + } | |
26 | + } | |
27 | + return threadPoll; | |
28 | + } | |
29 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/type/Gender.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/type/Gender.java | |
1 | +package com.diligrp.boss.shared.type; | |
2 | + | |
3 | +import java.util.Arrays; | |
4 | +import java.util.List; | |
5 | +import java.util.Optional; | |
6 | +import java.util.stream.Stream; | |
7 | + | |
8 | +public enum Gender implements IEnumType { | |
9 | + MALE("男", 1), | |
10 | + FEMALE("女", 2); | |
11 | + | |
12 | + private String name; | |
13 | + private int code; | |
14 | + | |
15 | + Gender(String name, int code) { | |
16 | + this.name = name; | |
17 | + this.code = code; | |
18 | + } | |
19 | + | |
20 | + public static Optional<Gender> getGender(int code) { | |
21 | + Stream<Gender> GENDERS = Arrays.stream(values()); | |
22 | + return GENDERS.filter((gender) -> gender.getCode() == code).findFirst(); | |
23 | + } | |
24 | + | |
25 | + public static String getName(int code) { | |
26 | + Stream<Gender> GENDERS = Arrays.stream(values()); | |
27 | + Optional<String> result = GENDERS.filter((gender) -> gender.getCode() == code) | |
28 | + .map(Gender::getName).findFirst(); | |
29 | + return result.isPresent() ? result.get() : null; | |
30 | + } | |
31 | + | |
32 | + public static List<Gender> getGenders() { | |
33 | + return Arrays.asList(values()); | |
34 | + } | |
35 | + | |
36 | + public String getName() { | |
37 | + return this.name; | |
38 | + } | |
39 | + | |
40 | + public int getCode() { | |
41 | + return this.code; | |
42 | + } | |
43 | + | |
44 | + public String toString() { | |
45 | + return this.name; | |
46 | + } | |
47 | +} | |
0 | 48 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/type/IEnumType.java
0 → 100644
boss-shared/src/main/java/com/diligrp/boss/shared/uid/KeyGenerator.java
0 → 100644
boss-shared/src/main/java/com/diligrp/boss/shared/uid/KeyGeneratorManager.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/KeyGeneratorManager.java | |
1 | +package com.diligrp.boss.shared.uid; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.PersistentSequenceKey; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.service.SequenceKeyService; | |
6 | +import com.diligrp.boss.shared.uid.pattern.PatternLayout; | |
7 | +import com.diligrp.boss.shared.util.AssertUtils; | |
8 | +import org.springframework.stereotype.Service; | |
9 | + | |
10 | +import java.time.LocalDate; | |
11 | +import java.util.concurrent.ConcurrentHashMap; | |
12 | +import java.util.concurrent.ConcurrentMap; | |
13 | +import java.util.concurrent.TimeUnit; | |
14 | +import java.util.concurrent.locks.Lock; | |
15 | +import java.util.concurrent.locks.ReentrantLock; | |
16 | + | |
17 | +@Service("keyGeneratorManager") | |
18 | +public class KeyGeneratorManager { | |
19 | + private final SequenceKeyService sequenceKeyService; | |
20 | + | |
21 | + private final Lock locker = new ReentrantLock(); | |
22 | + | |
23 | + private final ConcurrentMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>(); | |
24 | + | |
25 | + public KeyGeneratorManager(SequenceKeyService sequenceKeyService) { | |
26 | + this.sequenceKeyService = sequenceKeyService; | |
27 | + } | |
28 | + | |
29 | + public KeyGenerator getKeyGenerator(String key) { | |
30 | + AssertUtils.notNull(key, "Miss key parameter"); | |
31 | + | |
32 | + KeyGenerator keyGenerator = keyGenerators.get(key); | |
33 | + // First check, no need synchronize code block | |
34 | + if (keyGenerator == null) { | |
35 | + boolean result = false; | |
36 | + try { | |
37 | + result = locker.tryLock(15, TimeUnit.SECONDS); | |
38 | + if (!result) { | |
39 | + throw new RuntimeException("Timeout to get KeyGenerator for " + key); | |
40 | + } | |
41 | + | |
42 | + // Double check for performance purpose | |
43 | + if ((keyGenerator = keyGenerators.get(key)) == null) { | |
44 | + PersistentSequenceKey persistentKey = sequenceKeyService.findSequenceKey(key); | |
45 | + if (persistentKey.getExpiredOn() == null) { | |
46 | + keyGenerator = new KeyGeneratorImpl(persistentKey.getId(), key, persistentKey.getPattern()); | |
47 | + } else { | |
48 | + keyGenerator = new ExpiredKeyGeneratorImpl(persistentKey.getId(), key, persistentKey.getPattern()); | |
49 | + } | |
50 | + keyGenerators.put(key, keyGenerator); | |
51 | + } | |
52 | + } catch (InterruptedException iex) { | |
53 | + throw new RuntimeException("Interrupted to get KeyGenerator for " + key, iex); | |
54 | + } finally { | |
55 | + if (result) { | |
56 | + locker.unlock(); | |
57 | + } | |
58 | + } | |
59 | + } | |
60 | + | |
61 | + return keyGenerator; | |
62 | + } | |
63 | + | |
64 | + private class KeyGeneratorImpl implements KeyGenerator { | |
65 | + private final long id; | |
66 | + private final String key; | |
67 | + private final PatternLayout layout; | |
68 | + private long startWith; | |
69 | + private long endWith; | |
70 | + private final Lock keyLocker = new ReentrantLock(); | |
71 | + | |
72 | + public KeyGeneratorImpl(long id, String key, String pattern) { | |
73 | + this.id = id; | |
74 | + this.key = key; | |
75 | + this.startWith = 0; | |
76 | + this.endWith = -1; | |
77 | + if (pattern != null) { | |
78 | + this.layout = new PatternLayout(pattern); | |
79 | + } else { | |
80 | + this.layout = null; | |
81 | + } | |
82 | + } | |
83 | + | |
84 | + @Override | |
85 | + public String nextId() { | |
86 | + boolean result = false; | |
87 | + try { | |
88 | + result = keyLocker.tryLock(15L, TimeUnit.SECONDS); | |
89 | + if (!result) { | |
90 | + throw new RuntimeException("Timeout to get KeyGenerator for " + key); | |
91 | + } | |
92 | + | |
93 | + if (this.startWith <= this.endWith) { | |
94 | + if (this.layout != null) { | |
95 | + SequenceKey context = new SequenceKey(this.startWith++, LocalDate.now()); | |
96 | + return layout.doLayout(context); | |
97 | + } else { | |
98 | + return String.valueOf(this.startWith++); | |
99 | + } | |
100 | + } else { | |
101 | + PersistentSequenceKey sequenceKey = sequenceKeyService.synchronizeSequenceKey(id); | |
102 | + long newValue = sequenceKey.getValue() + sequenceKey.getStep(); | |
103 | + this.startWith = sequenceKey.getValue(); | |
104 | + this.endWith = newValue - 1; | |
105 | + | |
106 | + // Then recursive call for a next ID | |
107 | + return nextId(); | |
108 | + } | |
109 | + } catch (InterruptedException iex) { | |
110 | + throw new RuntimeException("Interrupted to get KeyGenerator for " + key, iex); | |
111 | + } finally { | |
112 | + if (result) { | |
113 | + keyLocker.unlock(); | |
114 | + } | |
115 | + } | |
116 | + } | |
117 | + } | |
118 | + | |
119 | + private class ExpiredKeyGeneratorImpl implements KeyGenerator { | |
120 | + private final long id; | |
121 | + private final String key; | |
122 | + private final PatternLayout layout; | |
123 | + | |
124 | + private ExpiredKeyGeneratorImpl(long id, String key, String pattern) { | |
125 | + this.id = id; | |
126 | + this.key = key; | |
127 | + if (pattern != null) { | |
128 | + this.layout = new PatternLayout(pattern); | |
129 | + } else { | |
130 | + this.layout = null; | |
131 | + } | |
132 | + } | |
133 | + | |
134 | + @Override | |
135 | + public String nextId() { | |
136 | + //悲观锁添加行锁 - 多JVM多线程场景下自动实现线程同步 | |
137 | + PersistentSequenceKey persistentKey = sequenceKeyService.synchronizeSequenceKey(id); | |
138 | + if (persistentKey == null) { | |
139 | + throw new RuntimeException("Unregistered service key generator: " + key); | |
140 | + } | |
141 | + | |
142 | + if (this.layout != null) { | |
143 | + SequenceKey context = new SequenceKey(persistentKey.getValue(), persistentKey.getToday()); | |
144 | + return layout.doLayout(context); | |
145 | + } else { | |
146 | + return String.valueOf(persistentKey.getValue()); | |
147 | + } | |
148 | + } | |
149 | + } | |
150 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/SnowflakeKeyManager.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/SnowflakeKeyManager.java | |
1 | +package com.diligrp.boss.shared.uid; | |
2 | + | |
3 | +import com.diligrp.boss.shared.util.AssertUtils; | |
4 | +import com.diligrp.boss.shared.util.DateUtils; | |
5 | +import com.diligrp.boss.shared.util.RandomUtils; | |
6 | +import org.springframework.stereotype.Service; | |
7 | + | |
8 | +import java.time.LocalDateTime; | |
9 | +import java.time.ZoneOffset; | |
10 | +import java.util.concurrent.ConcurrentHashMap; | |
11 | +import java.util.concurrent.ConcurrentMap; | |
12 | +import java.util.concurrent.TimeUnit; | |
13 | +import java.util.concurrent.locks.Lock; | |
14 | +import java.util.concurrent.locks.ReentrantLock; | |
15 | + | |
16 | +/** | |
17 | + * 雪花算法KeyManager实现 | |
18 | + */ | |
19 | +@Service("snowflakeKeyManager") | |
20 | +public class SnowflakeKeyManager { | |
21 | + | |
22 | + private final Lock locker = new ReentrantLock(); | |
23 | + | |
24 | + private final ConcurrentMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>(); | |
25 | + | |
26 | + public KeyGenerator getKeyGenerator(SnowflakeKey key) { | |
27 | + AssertUtils.notNull(key, "Miss key parameter"); | |
28 | + | |
29 | + String cachedKey = key.identifier(); | |
30 | + KeyGenerator keyGenerator = keyGenerators.get(cachedKey); | |
31 | + // First check, no need synchronize code block | |
32 | + if (keyGenerator == null) { | |
33 | + boolean result = false; | |
34 | + try { | |
35 | + result = locker.tryLock(15, TimeUnit.SECONDS); | |
36 | + if (!result) { | |
37 | + throw new RuntimeException("Timeout to get SnowflakeKeyGenerator for " + cachedKey); | |
38 | + } | |
39 | + | |
40 | + // Double check for performance purpose | |
41 | + if ((keyGenerator = keyGenerators.get(cachedKey)) == null) { | |
42 | + keyGenerator = new SnowflakeKeyGenerator(key.timeBits(), key.workerBits(), key.seqBits()); | |
43 | + keyGenerators.put(cachedKey, keyGenerator); | |
44 | + } | |
45 | + } catch (InterruptedException iex) { | |
46 | + throw new RuntimeException("Interrupted to get SnowflakeKeyGenerator for " + cachedKey, iex); | |
47 | + } finally { | |
48 | + if (result) { | |
49 | + locker.unlock(); | |
50 | + } | |
51 | + } | |
52 | + } | |
53 | + | |
54 | + return keyGenerator; | |
55 | + } | |
56 | + | |
57 | + /** | |
58 | + * 雪花算法ID生成器实现 | |
59 | + */ | |
60 | + private static class SnowflakeKeyGenerator implements KeyGenerator { | |
61 | + | |
62 | + /** | |
63 | + * Customer based epoch, unit as second. until 2020-08-08 00:00:00 | |
64 | + */ | |
65 | + private final long epochSeconds = LocalDateTime.of(2020, 8, 8, 0, 0, 0) | |
66 | + .toEpochSecond(ZoneOffset.of("+8")); | |
67 | + | |
68 | + /** | |
69 | + * Stable fields after spring bean initializing | |
70 | + */ | |
71 | + private final BitsAllocator bitsAllocator; | |
72 | + private final long workerId; | |
73 | + | |
74 | + /** | |
75 | + * Volatile fields caused by nextId() | |
76 | + */ | |
77 | + private long sequence = 0L; | |
78 | + private long lastSecond = -1L; | |
79 | + | |
80 | + public SnowflakeKeyGenerator(int timeBits, int workerBits, int seqBits) { | |
81 | + this.bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits); | |
82 | + this.workerId = bitsAllocator.assignWorkerId(); | |
83 | + } | |
84 | + | |
85 | + @Override | |
86 | + public synchronized String nextId() { | |
87 | + long currentSecond = getCurrentSecond(); | |
88 | + | |
89 | + // Clock moved backwards, refuse to generate uid | |
90 | + if (currentSecond < lastSecond) { | |
91 | + long refusedSeconds = lastSecond - currentSecond; | |
92 | + throw new RuntimeException(String.format("Clock moved backwards. Refusing for %d seconds", refusedSeconds)); | |
93 | + } | |
94 | + | |
95 | + // At the same second, increase service | |
96 | + if (currentSecond == lastSecond) { | |
97 | + sequence = (sequence + 1) & bitsAllocator.maxSequence; | |
98 | + // Exceed the max service, we wait the next second to generate uid | |
99 | + if (sequence == 0) { | |
100 | + currentSecond = getNextSecond(lastSecond); | |
101 | + } | |
102 | + | |
103 | + // At the different second, service restart from zero | |
104 | + } else { | |
105 | + sequence = 0L; | |
106 | + } | |
107 | + | |
108 | + lastSecond = currentSecond; | |
109 | + | |
110 | + // Allocate bits for UID | |
111 | + return String.valueOf(bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence)); | |
112 | + } | |
113 | + | |
114 | + public String parseId(long uid) { | |
115 | + long totalBits = BitsAllocator.TOTAL_BITS; | |
116 | + long signBits = bitsAllocator.signBits; | |
117 | + long timestampBits = bitsAllocator.timestampBits; | |
118 | + long workerIdBits = bitsAllocator.workerIdBits; | |
119 | + long sequenceBits = bitsAllocator.sequenceBits; | |
120 | + | |
121 | + // parse UID | |
122 | + long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits); | |
123 | + long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits); | |
124 | + long deltaSeconds = uid >>> (workerIdBits + sequenceBits); | |
125 | + | |
126 | + LocalDateTime when = LocalDateTime.ofEpochSecond(epochSeconds + deltaSeconds, 0, ZoneOffset.of("+8")); | |
127 | + String thatTime = DateUtils.formatDateTime(when); | |
128 | + | |
129 | + // format as string | |
130 | + return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"service\":\"%d\"}", | |
131 | + uid, thatTime, workerId, sequence); | |
132 | + } | |
133 | + | |
134 | + /** | |
135 | + * Get next millisecond | |
136 | + */ | |
137 | + private long getNextSecond(long lastTimestamp) { | |
138 | + long timestamp = getCurrentSecond(); | |
139 | + while (timestamp <= lastTimestamp) { | |
140 | + timestamp = getCurrentSecond(); | |
141 | + } | |
142 | + | |
143 | + return timestamp; | |
144 | + } | |
145 | + | |
146 | + /** | |
147 | + * Get current second | |
148 | + */ | |
149 | + private long getCurrentSecond() { | |
150 | + long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); | |
151 | + if (currentSecond - epochSeconds > bitsAllocator.maxDeltaSeconds) { | |
152 | + throw new RuntimeException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond); | |
153 | + } | |
154 | + | |
155 | + return currentSecond; | |
156 | + } | |
157 | + } | |
158 | + | |
159 | + /** | |
160 | + * Allocate 64 bits for the UID(long)<br> | |
161 | + * sign (fixed 1bit) -> deltaSecond -> workerId -> service(within the same second) | |
162 | + */ | |
163 | + private static class BitsAllocator { | |
164 | + /** | |
165 | + * Total 64 bits | |
166 | + */ | |
167 | + public static final int TOTAL_BITS = 1 << 6; | |
168 | + | |
169 | + /** | |
170 | + * Bits for [sign-> second-> workId-> service] | |
171 | + */ | |
172 | + private final int signBits = 1; | |
173 | + private final int timestampBits; | |
174 | + private final int workerIdBits; | |
175 | + private final int sequenceBits; | |
176 | + | |
177 | + /** | |
178 | + * Max value for workId & service | |
179 | + */ | |
180 | + private final long maxDeltaSeconds; | |
181 | + private final long maxWorkerId; | |
182 | + private final long maxSequence; | |
183 | + | |
184 | + /** | |
185 | + * Shift for timestamp & workerId | |
186 | + */ | |
187 | + private final int timestampShift; | |
188 | + private final int workerIdShift; | |
189 | + | |
190 | + /** | |
191 | + * Constructor with timestampBits, workerIdBits, sequenceBits<br> | |
192 | + * The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits | |
193 | + */ | |
194 | + public BitsAllocator(int timestampBits, int workerIdBits, int sequenceBits) { | |
195 | + // make sure allocated 64 bits | |
196 | + int allocateTotalBits = signBits + timestampBits + workerIdBits + sequenceBits; | |
197 | + AssertUtils.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits"); | |
198 | + | |
199 | + // initialize bits | |
200 | + this.timestampBits = timestampBits; | |
201 | + this.workerIdBits = workerIdBits; | |
202 | + this.sequenceBits = sequenceBits; | |
203 | + | |
204 | + // initialize max value | |
205 | + this.maxDeltaSeconds = ~(-1L << timestampBits); | |
206 | + this.maxWorkerId = ~(-1L << workerIdBits); | |
207 | + this.maxSequence = ~(-1L << sequenceBits); | |
208 | + | |
209 | + // initialize shift | |
210 | + this.timestampShift = workerIdBits + sequenceBits; | |
211 | + this.workerIdShift = sequenceBits; | |
212 | + } | |
213 | + | |
214 | + /** | |
215 | + * Allocate bits for UID according to delta seconds & workerId & service<br> | |
216 | + * <b>Note that: </b>The highest bit will always be 0 for sign | |
217 | + */ | |
218 | + public long allocate(long deltaSeconds, long workerId, long sequence) { | |
219 | + return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence; | |
220 | + } | |
221 | + | |
222 | + /** | |
223 | + * Assign worker id using hash and random number, avoid worker id conflict | |
224 | + */ | |
225 | + public long assignWorkerId() { | |
226 | + int h; | |
227 | + // First hash and index via maxWorkerId, see HashMap | |
228 | + String randomWorkerId = RandomUtils.randomUUID(); | |
229 | + int hash = (h = randomWorkerId.hashCode()) ^ h >>> 16; | |
230 | + long workerId = (maxWorkerId - 1) & hash; | |
231 | + | |
232 | + // Secondly plus random number | |
233 | + int duration = (int)(maxWorkerId - workerId); | |
234 | + if (duration > 10) { | |
235 | + workerId += RandomUtils.randomInt(1, duration); | |
236 | + } | |
237 | + return workerId; | |
238 | + } | |
239 | + } | |
240 | + | |
241 | + public interface SnowflakeKey { | |
242 | + // 默认时间戳位数 | |
243 | + int timeBits = 28; | |
244 | + // 默认机器标识位数 | |
245 | + int workerBits = 22; | |
246 | + // 默认序号位数 | |
247 | + int seqBits = 13; | |
248 | + | |
249 | + default int timeBits() { | |
250 | + return timeBits; | |
251 | + } | |
252 | + | |
253 | + default int workerBits() { | |
254 | + return workerBits; | |
255 | + } | |
256 | + | |
257 | + default int seqBits() { | |
258 | + return seqBits; | |
259 | + } | |
260 | + | |
261 | + default String identifier() { | |
262 | + return this.toString(); | |
263 | + } | |
264 | + } | |
265 | +} | |
0 | 266 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/Converter.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/Converter.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +public abstract class Converter<T> { | |
4 | + private Converter<T> next; | |
5 | + | |
6 | + public abstract String convert(T t); | |
7 | + | |
8 | + public Converter<T> getNext() { | |
9 | + return next; | |
10 | + } | |
11 | + | |
12 | + public void setNext(Converter<T> next) { | |
13 | + this.next = next; | |
14 | + } | |
15 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/DateConverter.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/DateConverter.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
6 | + | |
7 | +import java.time.format.DateTimeFormatter; | |
8 | + | |
9 | +public class DateConverter extends Converter<SequenceKey> { | |
10 | + private static final String DEFAULT_FORMAT = "yyyyMMdd"; | |
11 | + | |
12 | + private final String format; | |
13 | + | |
14 | + public DateConverter(String format) { | |
15 | + if (format != null) { | |
16 | + try { | |
17 | + DateTimeFormatter.ofPattern(format); | |
18 | + } catch (Exception ex) { | |
19 | + throw new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Invalid date format"); | |
20 | + } | |
21 | + this.format = format; | |
22 | + } else { | |
23 | + this.format = DEFAULT_FORMAT; | |
24 | + } | |
25 | + } | |
26 | + | |
27 | + @Override | |
28 | + public String convert(SequenceKey context) { | |
29 | + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format); | |
30 | + return context.getWhen().format(formatter); | |
31 | + } | |
32 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/KeywordToken.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/KeywordToken.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
6 | + | |
7 | +public class KeywordToken extends Token { | |
8 | + public KeywordToken(String token) { | |
9 | + super(token); | |
10 | + } | |
11 | + | |
12 | + @Override | |
13 | + Converter<SequenceKey> getConverter() { | |
14 | + if ("d".equals(token) || "date".equals(token)) { | |
15 | + return new DateConverter(option); | |
16 | + } else if ("n".equals(token)) { | |
17 | + return new SequenceConverter(option); | |
18 | + } else if ("r".equals(token)) { | |
19 | + return new RandomConverter(option); | |
20 | + } else { | |
21 | + throw new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Unrecognized keyword " + token); | |
22 | + } | |
23 | + } | |
24 | + | |
25 | + public String toString() { | |
26 | + return String.format("keyword(%s)", token); | |
27 | + } | |
28 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/LiteralConverter.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/LiteralConverter.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
4 | + | |
5 | +public class LiteralConverter extends Converter<SequenceKey> { | |
6 | + private final String literal; | |
7 | + | |
8 | + public LiteralConverter(String literal) { | |
9 | + this.literal = literal; | |
10 | + } | |
11 | + | |
12 | + @Override | |
13 | + public String convert(SequenceKey context) { | |
14 | + return literal; | |
15 | + } | |
16 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/LiteralToken.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/LiteralToken.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
4 | + | |
5 | +public class LiteralToken extends Token { | |
6 | + public LiteralToken(String token) { | |
7 | + super(token); | |
8 | + } | |
9 | + | |
10 | + @Override | |
11 | + Converter<SequenceKey> getConverter() { | |
12 | + return new LiteralConverter(token); | |
13 | + } | |
14 | + | |
15 | + public String toString() { | |
16 | + return String.format("literal(%s)", token); | |
17 | + } | |
18 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/OptionToken.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/OptionToken.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
6 | + | |
7 | +public class OptionToken extends Token { | |
8 | + public OptionToken(String token) { | |
9 | + super(token); | |
10 | + } | |
11 | + | |
12 | + public String getToken() { | |
13 | + return this.token; | |
14 | + } | |
15 | + | |
16 | + @Override | |
17 | + Converter<SequenceKey> getConverter() { | |
18 | + throw new PlatformServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Not supported converter"); | |
19 | + } | |
20 | + | |
21 | + public String toString() { | |
22 | + return String.format("option(%s)", token); | |
23 | + } | |
24 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/PatternLayout.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/PatternLayout.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
4 | + | |
5 | +import java.util.ArrayList; | |
6 | +import java.util.List; | |
7 | + | |
8 | +public class PatternLayout { | |
9 | + | |
10 | + private final Converter<SequenceKey> head; | |
11 | + | |
12 | + public PatternLayout(String pattern) { | |
13 | + this.head = compile(pattern); | |
14 | + } | |
15 | + | |
16 | + public String doLayout(SequenceKey context) { | |
17 | + StringBuilder writer = new StringBuilder(); | |
18 | + Converter<SequenceKey> converter = head; | |
19 | + | |
20 | + while (converter != null) { | |
21 | + writer.append(converter.convert(context)); | |
22 | + converter = converter.getNext(); | |
23 | + } | |
24 | + return writer.toString(); | |
25 | + } | |
26 | + | |
27 | + private Converter<SequenceKey> compile(String pattern) { | |
28 | + PatternParser parser = new PatternParser(pattern); | |
29 | + List<Token> tokens = new ArrayList<>(); | |
30 | + Token previous = null; | |
31 | + for (Token token : parser.parse()) { | |
32 | + if (token instanceof KeywordToken || token instanceof LiteralToken) { | |
33 | + previous = token; | |
34 | + tokens.add(token); | |
35 | + } else if (token instanceof OptionToken) { | |
36 | + if (previous != null) { | |
37 | + previous.setOption(((OptionToken) token).getToken()); | |
38 | + } | |
39 | + } | |
40 | + } | |
41 | + | |
42 | + Converter<SequenceKey> first = tokens.get(0).getConverter(); | |
43 | + Converter<SequenceKey> current = first; | |
44 | + for (int i = 1; i < tokens.size(); i++) { | |
45 | + Token token = tokens.get(i); | |
46 | + current.setNext(token.getConverter()); | |
47 | + current = current.getNext(); | |
48 | + } | |
49 | + | |
50 | + return first; | |
51 | + } | |
52 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/PatternParser.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/PatternParser.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
5 | + | |
6 | +import java.util.ArrayList; | |
7 | +import java.util.List; | |
8 | + | |
9 | +public class PatternParser { | |
10 | + private final String pattern; | |
11 | + private final int length; | |
12 | + private TokenizerState state; | |
13 | + private int index; | |
14 | + | |
15 | + public PatternParser(String pattern) { | |
16 | + this.pattern = pattern; | |
17 | + this.length = pattern.length(); | |
18 | + this.index = 0; | |
19 | + this.state = TokenizerState.LITERAL_STATE; | |
20 | + } | |
21 | + | |
22 | + public List<Token> parse() { | |
23 | + List<Token> tokens = new ArrayList<>(); | |
24 | + StringBuilder buf = new StringBuilder(); | |
25 | + | |
26 | + while(index < length) { | |
27 | + char c = pattern.charAt(index); | |
28 | + index ++; | |
29 | + | |
30 | + switch (this.state) { | |
31 | + case LITERAL_STATE: | |
32 | + handleLiteralState(c, tokens, buf); | |
33 | + break; | |
34 | + case KEYWORD_STATE: | |
35 | + handleKeywordState(c, tokens, buf); | |
36 | + break; | |
37 | + case OPTION_STATE: | |
38 | + handleOptionState(c, tokens, buf); | |
39 | + break; | |
40 | + } | |
41 | + } | |
42 | + | |
43 | + switch (state) { | |
44 | + case LITERAL_STATE: | |
45 | + handleLiteralState('%', tokens, buf); | |
46 | + break; | |
47 | + case KEYWORD_STATE: | |
48 | + handleKeywordState('%', tokens, buf); | |
49 | + break; | |
50 | + default: | |
51 | + throw new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Unexpected end of pattern string"); | |
52 | + } | |
53 | + | |
54 | + return tokens; | |
55 | + } | |
56 | + | |
57 | + private void handleLiteralState(char c, List<Token> tokens, StringBuilder buf) { | |
58 | + switch (c) { | |
59 | + case '%': | |
60 | + addLiteralToken(buf, tokens); | |
61 | + state = TokenizerState.KEYWORD_STATE; | |
62 | + break; | |
63 | + default: | |
64 | + buf.append(c); | |
65 | + } | |
66 | + } | |
67 | + | |
68 | + private void handleKeywordState(char c, List<Token> tokens, StringBuilder buf) { | |
69 | + switch (c) { | |
70 | + case '%': | |
71 | + addKeywordToken(buf, tokens); | |
72 | + state = TokenizerState.KEYWORD_STATE; | |
73 | + break; | |
74 | + case '{': | |
75 | + this.addKeywordToken(buf, tokens); | |
76 | + this.state = TokenizerState.OPTION_STATE; | |
77 | + break; | |
78 | + default: | |
79 | + buf.append(c); | |
80 | + } | |
81 | + } | |
82 | + | |
83 | + private void handleOptionState(char c, List<Token> tokens, StringBuilder buf) { | |
84 | + switch (c) { | |
85 | + case '}': | |
86 | + addOptionToken(buf, tokens); | |
87 | + state = TokenizerState.LITERAL_STATE; | |
88 | + break; | |
89 | + default: | |
90 | + buf.append(c); | |
91 | + } | |
92 | + } | |
93 | + | |
94 | + private void addLiteralToken(StringBuilder buf, List<Token> tokens) { | |
95 | + if (!buf.isEmpty()) { | |
96 | + tokens.add(new LiteralToken(buf.toString())); | |
97 | + buf.setLength(0); | |
98 | + } | |
99 | + } | |
100 | + | |
101 | + private void addKeywordToken(StringBuilder buf, List<Token> tokens) { | |
102 | + if (!buf.isEmpty()) { | |
103 | + tokens.add(new KeywordToken(buf.toString())); | |
104 | + buf.setLength(0); | |
105 | + } | |
106 | + } | |
107 | + | |
108 | + private void addOptionToken(StringBuilder buf, List<Token> tokens) { | |
109 | + if (!buf.isEmpty()) { | |
110 | + tokens.add(new OptionToken(buf.toString())); | |
111 | + buf.setLength(0); | |
112 | + } | |
113 | + } | |
114 | + | |
115 | + private enum TokenizerState { | |
116 | + LITERAL_STATE, | |
117 | + KEYWORD_STATE, | |
118 | + OPTION_STATE | |
119 | + } | |
120 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/RandomConverter.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/RandomConverter.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
6 | +import com.diligrp.boss.shared.util.RandomUtils; | |
7 | + | |
8 | +public class RandomConverter extends Converter<SequenceKey> { | |
9 | + private static final int DEFAULT_LENGTH = 1; | |
10 | + | |
11 | + private final int length; | |
12 | + | |
13 | + public RandomConverter(String length) { | |
14 | + if (length != null) { | |
15 | + try { | |
16 | + this.length = Integer.parseInt(length); | |
17 | + } catch (Exception ex) { | |
18 | + throw new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Invalid length for %r token"); | |
19 | + } | |
20 | + } else { | |
21 | + this.length = DEFAULT_LENGTH; | |
22 | + } | |
23 | + } | |
24 | + | |
25 | + @Override | |
26 | + public String convert(SequenceKey context) { | |
27 | + return RandomUtils.randomNumber(length); | |
28 | + } | |
29 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/SequenceConverter.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/SequenceConverter.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.ErrorCode; | |
4 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
5 | +import com.diligrp.boss.shared.exception.PlatformServiceException; | |
6 | + | |
7 | +public class SequenceConverter extends Converter<SequenceKey> { | |
8 | + private static final int DEFAULT_LENGTH = 4; | |
9 | + | |
10 | + private final int minLength; | |
11 | + | |
12 | + public SequenceConverter(String minLength) { | |
13 | + if (minLength != null) { | |
14 | + try { | |
15 | + this.minLength = Integer.parseInt(minLength); | |
16 | + } catch (Exception ex) { | |
17 | + throw new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Invalid minLength for %n token"); | |
18 | + } | |
19 | + } else { | |
20 | + this.minLength = DEFAULT_LENGTH; | |
21 | + } | |
22 | + } | |
23 | + | |
24 | + @Override | |
25 | + public String convert(SequenceKey context) { | |
26 | + StringBuilder buffer = new StringBuilder(); | |
27 | + buffer.append(context.getSequence()); | |
28 | + int length = buffer.length(); | |
29 | + if (length < minLength) { | |
30 | + for (int i = length; i < minLength; i++) { | |
31 | + buffer.insert(0, "0"); | |
32 | + } | |
33 | + } | |
34 | + return buffer.toString(); | |
35 | + } | |
36 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/Token.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/uid/pattern/Token.java | |
1 | +package com.diligrp.boss.shared.uid.pattern; | |
2 | + | |
3 | +import com.diligrp.boss.shared.domain.SequenceKey; | |
4 | + | |
5 | +public abstract class Token { | |
6 | + protected final String token; | |
7 | + | |
8 | + protected String option; | |
9 | + | |
10 | + public Token(String token) { | |
11 | + this.token = token; | |
12 | + } | |
13 | + | |
14 | + public void setOption(String option) { | |
15 | + this.option = option; | |
16 | + } | |
17 | + | |
18 | + abstract Converter<SequenceKey> getConverter(); | |
19 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/AssertUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/AssertUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.util.Collection; | |
4 | +import java.util.Map; | |
5 | + | |
6 | +/** | |
7 | + * 断言工具类 | |
8 | + */ | |
9 | +public class AssertUtils { | |
10 | + public static void notNull(Object object) { | |
11 | + notNull(object, "[Assertion failed] - this argument is required; it must not be null"); | |
12 | + } | |
13 | + | |
14 | + public static void notNull(Object object, String message) { | |
15 | + if (object == null) { | |
16 | + throw new IllegalArgumentException(message); | |
17 | + } | |
18 | + } | |
19 | + | |
20 | + public static void notEmpty(String str) { | |
21 | + notEmpty(str, "[Assertion failed] - this argument is required; it must not be empty"); | |
22 | + } | |
23 | + | |
24 | + public static void notEmpty(String str, String message) { | |
25 | + if (ObjectUtils.isEmpty(str)) { | |
26 | + throw new IllegalArgumentException(message); | |
27 | + } | |
28 | + } | |
29 | + | |
30 | + public static void isTrue(boolean expression, String message) { | |
31 | + if (!expression) { | |
32 | + throw new IllegalArgumentException(message); | |
33 | + } | |
34 | + } | |
35 | + | |
36 | + public static void isTrue(boolean expression) { | |
37 | + isTrue(expression, "[Assertion failed] - this expression must be true"); | |
38 | + } | |
39 | + | |
40 | + public static void notEmpty(Collection<?> collection, String message) { | |
41 | + if (collection == null || collection.isEmpty()) { | |
42 | + throw new IllegalArgumentException(message); | |
43 | + } | |
44 | + } | |
45 | + | |
46 | + public static void notEmpty(Collection<?> collection) { | |
47 | + notEmpty(collection, "[Assertion failed] - this collection must not be empty"); | |
48 | + } | |
49 | + | |
50 | + public static void notEmpty(Map<?, ?> map, String message) { | |
51 | + if (map == null || map.isEmpty()) { | |
52 | + throw new IllegalArgumentException(message); | |
53 | + } | |
54 | + } | |
55 | + | |
56 | + public static void notEmpty(Object[] array) { | |
57 | + notEmpty(array, "[Assertion failed] - this array must not be empty"); | |
58 | + } | |
59 | + | |
60 | + public static void notEmpty(Object[] array, String message) { | |
61 | + if (array == null || array.length == 0) { | |
62 | + throw new IllegalArgumentException(message); | |
63 | + } | |
64 | + } | |
65 | + | |
66 | + public static void notEmpty(Map<?, ?> map) { | |
67 | + notEmpty(map, "[Assertion failed] - this map must not be empty"); | |
68 | + } | |
69 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/ClassUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/ClassUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +/** | |
4 | + * Classloader工具类 | |
5 | + */ | |
6 | +public class ClassUtils { | |
7 | + public static ClassLoader getDefaultClassLoader() { | |
8 | + ClassLoader cl = null; | |
9 | + try { | |
10 | + cl = Thread.currentThread().getContextClassLoader(); | |
11 | + } catch (Throwable ex) { | |
12 | + // Cannot access thread context ClassLoader - falling back... | |
13 | + } | |
14 | + | |
15 | + if (cl == null) { | |
16 | + // No thread context class loader -> use class loader of this class. | |
17 | + cl = ClassUtils.class.getClassLoader(); | |
18 | + if (cl == null) { | |
19 | + // getClassLoader() returning null indicates the bootstrap ClassLoader | |
20 | + try { | |
21 | + cl = ClassLoader.getSystemClassLoader(); | |
22 | + } catch (Throwable ex) { | |
23 | + // Cannot access system ClassLoader - oh well, maybe the caller can live with null... | |
24 | + } | |
25 | + } | |
26 | + } | |
27 | + return cl; | |
28 | + } | |
29 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/CurrencyUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/CurrencyUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.math.BigDecimal; | |
4 | +import java.math.BigInteger; | |
5 | +import java.math.RoundingMode; | |
6 | +import java.text.FieldPosition; | |
7 | +import java.text.NumberFormat; | |
8 | +import java.util.Locale; | |
9 | + | |
10 | +/** | |
11 | + * 金额格式转化工具类 | |
12 | + */ | |
13 | +public class CurrencyUtils { | |
14 | + private static final int DEFAULT_SCALE = 2; | |
15 | + private static final Locale CURRENT_LOCALE = Locale.CHINA; | |
16 | + private static final BigDecimal YUAN_CENT_UNIT = new BigDecimal(100); | |
17 | + | |
18 | + public static String toCurrency(Long cent) { | |
19 | + if (cent == null) { | |
20 | + return null; | |
21 | + } | |
22 | + | |
23 | + BigDecimal amount = new BigDecimal(cent); | |
24 | + BigDecimal yuan = amount.divide(YUAN_CENT_UNIT).setScale(DEFAULT_SCALE, RoundingMode.HALF_UP); | |
25 | + | |
26 | + NumberFormat numberFormat = NumberFormat.getCurrencyInstance(CURRENT_LOCALE); | |
27 | + StringBuffer currency = new StringBuffer(); | |
28 | + numberFormat.format(yuan, currency, new FieldPosition(0)); | |
29 | + if (cent < 0) { | |
30 | + correctSymbol(currency); | |
31 | + } | |
32 | + return currency.toString(); | |
33 | + } | |
34 | + | |
35 | + public static String toNoSymbolCurrency(Long cent) { | |
36 | + if (cent == null) { | |
37 | + return null; | |
38 | + } | |
39 | + | |
40 | + BigDecimal amount = new BigDecimal(cent); | |
41 | + BigDecimal yuan = amount.divide(YUAN_CENT_UNIT).setScale(DEFAULT_SCALE, RoundingMode.HALF_UP); | |
42 | + | |
43 | + NumberFormat numberFormat = NumberFormat.getCurrencyInstance(CURRENT_LOCALE); | |
44 | + StringBuffer currency = new StringBuffer(); | |
45 | + numberFormat.format(yuan, currency, new FieldPosition(0)); | |
46 | + if (cent < 0) { | |
47 | + correctSymbol(currency); | |
48 | + } | |
49 | + return currency.substring(1); | |
50 | + } | |
51 | + | |
52 | + public static String cent2TenNoSymbol(Long cent) { | |
53 | + | |
54 | + BigDecimal yuan = point2ten(cent); | |
55 | + if (null == yuan) { | |
56 | + yuan = new BigDecimal(0L); | |
57 | + } | |
58 | + return yuan.toString(); | |
59 | + } | |
60 | + | |
61 | + public static Long yuan2Cent(BigDecimal yuan) { | |
62 | + if (yuan == null) { | |
63 | + return null; | |
64 | + } | |
65 | + | |
66 | + BigDecimal amount = yuan.setScale(DEFAULT_SCALE, RoundingMode.HALF_UP); | |
67 | + BigDecimal cent = amount.multiply(YUAN_CENT_UNIT); | |
68 | + return cent.longValue(); | |
69 | + } | |
70 | + | |
71 | + private static BigDecimal point2ten(Long point) { | |
72 | + if (null == point) { | |
73 | + point = 0L; | |
74 | + } | |
75 | + BigDecimal centBigDecimal = new BigDecimal(point); | |
76 | + BigInteger divisor = BigInteger.valueOf(1L); | |
77 | + for (int i = 0; i < DEFAULT_SCALE; i++) { | |
78 | + divisor = divisor.multiply(BigInteger.valueOf(10L)); | |
79 | + } | |
80 | + return centBigDecimal.divide(new BigDecimal(divisor)).setScale(DEFAULT_SCALE); | |
81 | + } | |
82 | + | |
83 | + public static String convert2Percent(Long total, Long percentNumber) { | |
84 | + if (percentNumber == 0L || total == 0L) { | |
85 | + return "0.00%"; | |
86 | + } | |
87 | + double percent = percentNumber.doubleValue() / total.doubleValue() * 100; | |
88 | + BigDecimal bigDecimal = new BigDecimal(percent); | |
89 | + return bigDecimal.setScale(2, RoundingMode.HALF_UP) + "%"; | |
90 | + } | |
91 | + | |
92 | + /** | |
93 | + * $-100.00 => -$100.00 | |
94 | + */ | |
95 | + private static void correctSymbol(StringBuffer currency) { | |
96 | + char negativeSymbol = currency.charAt(0); | |
97 | + char currencySymbol = currency.charAt(1); | |
98 | + currency.setCharAt(0, currencySymbol); | |
99 | + currency.setCharAt(1, negativeSymbol); | |
100 | + } | |
101 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/DateUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/DateUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import com.diligrp.boss.shared.Constants; | |
4 | + | |
5 | +import java.text.SimpleDateFormat; | |
6 | +import java.time.LocalDate; | |
7 | +import java.time.LocalDateTime; | |
8 | +import java.time.ZoneOffset; | |
9 | +import java.time.format.DateTimeFormatter; | |
10 | +import java.util.Date; | |
11 | + | |
12 | +/** | |
13 | + * 日期格式转化工具类 - JDK1.8 TIME API | |
14 | + */ | |
15 | +public class DateUtils { | |
16 | + | |
17 | + public static String formatDateTime(LocalDateTime when, String format) { | |
18 | + if (ObjectUtils.isNull(when)) { | |
19 | + return null; | |
20 | + } | |
21 | + | |
22 | + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format); | |
23 | + return when.format(formatter); | |
24 | + } | |
25 | + | |
26 | + public static String formatDateTime(LocalDateTime when) { | |
27 | + return formatDateTime(when, Constants.DATE_TIME_FORMAT); | |
28 | + } | |
29 | + | |
30 | + public static String formatDate(LocalDate when, String format) { | |
31 | + if (ObjectUtils.isNull(when)) { | |
32 | + return null; | |
33 | + } | |
34 | + | |
35 | + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format); | |
36 | + return when.format(formatter); | |
37 | + } | |
38 | + | |
39 | + public static String formatDate(LocalDate when) { | |
40 | + return formatDate(when, Constants.DATE_FORMAT); | |
41 | + } | |
42 | + | |
43 | + public static String formatNow(String format) { | |
44 | + return formatDateTime(LocalDateTime.now(), format); | |
45 | + } | |
46 | + | |
47 | + public static String formatNow() { | |
48 | + return formatNow(Constants.DATE_TIME_FORMAT); | |
49 | + } | |
50 | + | |
51 | + public static String format(Date date) { | |
52 | + return format(date, Constants.DATE_TIME_FORMAT); | |
53 | + } | |
54 | + | |
55 | + public static LocalDateTime addDays(long amount) { | |
56 | + LocalDateTime localDateTime = LocalDateTime.now(); | |
57 | + localDateTime.plusDays(amount); | |
58 | + return localDateTime; | |
59 | + } | |
60 | + | |
61 | + public static String format(Date date, String format) { | |
62 | + if (ObjectUtils.isNull(date)) { | |
63 | + return null; | |
64 | + } | |
65 | + | |
66 | + SimpleDateFormat sdf = new SimpleDateFormat(format); | |
67 | + return sdf.format(date); | |
68 | + | |
69 | + } | |
70 | + | |
71 | + public static LocalDateTime parseDateTime(String datetimeStr, String format) { | |
72 | + if (ObjectUtils.isEmpty(datetimeStr)) { | |
73 | + return null; | |
74 | + } | |
75 | + | |
76 | + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format); | |
77 | + return LocalDateTime.parse(datetimeStr, formatter); | |
78 | + } | |
79 | + | |
80 | + public static LocalDateTime parseDateTime(String datetimeStr) { | |
81 | + return parseDateTime(datetimeStr, Constants.DATE_TIME_FORMAT); | |
82 | + } | |
83 | + | |
84 | + public static LocalDate parseDate(String dateStr, String format) { | |
85 | + if (ObjectUtils.isEmpty(dateStr)) { | |
86 | + return null; | |
87 | + } | |
88 | + | |
89 | + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format); | |
90 | + return LocalDate.parse(dateStr, formatter); | |
91 | + } | |
92 | + | |
93 | + public static LocalDate parseDate(String dateStr) { | |
94 | + return parseDate(dateStr, Constants.DATE_FORMAT); | |
95 | + } | |
96 | + | |
97 | + public static Date parse(String dateStr) { | |
98 | + return parse(dateStr, Constants.DATE_TIME_FORMAT); | |
99 | + } | |
100 | + | |
101 | + public static Date parse(String dateStr, String format) { | |
102 | + if (ObjectUtils.isEmpty(dateStr)) { | |
103 | + return null; | |
104 | + } | |
105 | + | |
106 | + try { | |
107 | + SimpleDateFormat sdf = new SimpleDateFormat(format); | |
108 | + return sdf.parse(dateStr); | |
109 | + } catch (Exception ex) { | |
110 | + throw new IllegalArgumentException("Invalid date format", ex); | |
111 | + } | |
112 | + } | |
113 | + | |
114 | + /** | |
115 | + * 获取时间戳 | |
116 | + */ | |
117 | + public static long parseMilliSecond(LocalDateTime localDateTime){ | |
118 | + return parseMilliSecond(localDateTime,null); | |
119 | + } | |
120 | + | |
121 | + public static long parseMilliSecond(LocalDateTime localDateTime, String zoneNumStr){ | |
122 | + //默认东八区 | |
123 | + if (ObjectUtils.isEmpty(zoneNumStr)){ | |
124 | + zoneNumStr = "+8"; | |
125 | + } | |
126 | + return localDateTime.toInstant(ZoneOffset.of(zoneNumStr)).toEpochMilli(); | |
127 | + } | |
128 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/JsonUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/JsonUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import com.diligrp.boss.shared.Constants; | |
4 | +import com.fasterxml.jackson.annotation.JsonInclude; | |
5 | +import com.fasterxml.jackson.core.JsonProcessingException; | |
6 | +import com.fasterxml.jackson.core.type.TypeReference; | |
7 | +import com.fasterxml.jackson.databind.DeserializationFeature; | |
8 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
9 | +import com.fasterxml.jackson.databind.SerializationFeature; | |
10 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | |
11 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; | |
12 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; | |
13 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; | |
14 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; | |
15 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | |
16 | +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; | |
17 | + | |
18 | +import java.text.SimpleDateFormat; | |
19 | +import java.time.LocalDate; | |
20 | +import java.time.LocalDateTime; | |
21 | +import java.time.LocalTime; | |
22 | +import java.time.ZoneOffset; | |
23 | +import java.time.format.DateTimeFormatter; | |
24 | +import java.util.TimeZone; | |
25 | + | |
26 | +public class JsonUtils { | |
27 | + | |
28 | + private static ObjectMapper objectMapper = initObjectMapper(); | |
29 | + | |
30 | + private static ObjectMapper initObjectMapper(){ | |
31 | + Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder(); | |
32 | + initObjectMapperBuilder(jackson2ObjectMapperBuilder); | |
33 | + ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build(); | |
34 | + objectMapper.setSerializerFactory(objectMapper.getSerializerFactory()); | |
35 | + return objectMapper; | |
36 | + } | |
37 | + | |
38 | + public static void initObjectMapperBuilder(Jackson2ObjectMapperBuilder builder) { | |
39 | + //序列化java.util.Date类型 | |
40 | + builder.dateFormat(new SimpleDateFormat(Constants.DATE_TIME_FORMAT)); | |
41 | + builder.timeZone(TimeZone.getTimeZone(ZoneOffset.of("+8"))); | |
42 | + builder.serializationInclusion(JsonInclude.Include.NON_NULL); | |
43 | + builder.featuresToDisable( | |
44 | + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, // Json串的属性无JavaBean字段对应时,避免抛出异常 | |
45 | + DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, // JavaBean中primitive类型的字段无Json属性时,避免抛出异常 | |
46 | + DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, // Json串数字类型属性,赋值JavaBean中Enum字段时,避免抛出异常 | |
47 | + SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, | |
48 | + SerializationFeature.FAIL_ON_EMPTY_BEANS | |
49 | + ); | |
50 | + builder.featuresToEnable( | |
51 | + DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, | |
52 | + DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY | |
53 | + ); | |
54 | + | |
55 | + var dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); | |
56 | + var dateFormatter = DateTimeFormatter.ofPattern(Constants.DATE_FORMAT); | |
57 | + var timeFormatter = DateTimeFormatter.ofPattern(Constants.TIME_FORMAT); | |
58 | + // 添加自定义序列化 | |
59 | + builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); | |
60 | + builder.serializerByType(LocalDate.class, new LocalDateSerializer(dateFormatter)); | |
61 | + builder.serializerByType(LocalTime.class, new LocalTimeSerializer(timeFormatter)); | |
62 | + // 添加自定义反序列化 | |
63 | + builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter)); | |
64 | + builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(dateFormatter)); | |
65 | + builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(timeFormatter)); | |
66 | + } | |
67 | + | |
68 | + public static <T> T fromJsonString(String json, Class<T> type) { | |
69 | + try { | |
70 | + return objectMapper.readValue(json, type); | |
71 | + } catch (JsonProcessingException ex) { | |
72 | + throw new IllegalArgumentException("Deserialize json exception", ex); | |
73 | + } | |
74 | + } | |
75 | + | |
76 | + public static <T> T fromJsonString(String json, TypeReference<T> jsonTypeReference){ | |
77 | + try { | |
78 | + return objectMapper.readValue(json, jsonTypeReference); | |
79 | + } catch (JsonProcessingException ex) { | |
80 | + throw new IllegalArgumentException("Deserialize json array exception", ex); | |
81 | + } | |
82 | + } | |
83 | + | |
84 | + public static <T> String toJsonString(T object) { | |
85 | + try { | |
86 | + return objectMapper.writeValueAsString(object); | |
87 | + } catch (JsonProcessingException ex) { | |
88 | + throw new IllegalArgumentException("Serialize json exception", ex); | |
89 | + } | |
90 | + } | |
91 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/MathUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/MathUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.math.BigDecimal; | |
4 | +import java.math.RoundingMode; | |
5 | + | |
6 | +/** | |
7 | + * 精确的浮点数运算 | |
8 | + */ | |
9 | +public class MathUtils { | |
10 | + | |
11 | + /** | |
12 | + * 默认除法运算精度 | |
13 | + */ | |
14 | + private static final int DEF_DIV_SCALE = 10; | |
15 | + | |
16 | + /** | |
17 | + * 提供精确的加法运算 | |
18 | + */ | |
19 | + public static double add(double v1, double v2) { | |
20 | + BigDecimal b1 = new BigDecimal(Double.toString(v1)); | |
21 | + BigDecimal b2 = new BigDecimal(Double.toString(v2)); | |
22 | + return b1.add(b2).doubleValue(); | |
23 | + } | |
24 | + | |
25 | + /** | |
26 | + * 提供精确的减法运算。 | |
27 | + */ | |
28 | + public static double sub(double v1, double v2) { | |
29 | + BigDecimal b1 = new BigDecimal(Double.toString(v1)); | |
30 | + BigDecimal b2 = new BigDecimal(Double.toString(v2)); | |
31 | + return b1.subtract(b2).doubleValue(); | |
32 | + } | |
33 | + | |
34 | + /** | |
35 | + * 提供精确的乘法运算。 | |
36 | + */ | |
37 | + public static double mul(double v1, double v2) { | |
38 | + BigDecimal b1 = new BigDecimal(Double.toString(v1)); | |
39 | + BigDecimal b2 = new BigDecimal(Double.toString(v2)); | |
40 | + return b1.multiply(b2).doubleValue(); | |
41 | + } | |
42 | + | |
43 | + /** | |
44 | + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到小数点以后10位, | |
45 | + * 以后的数字四舍五入。 | |
46 | + */ | |
47 | + public static double div(double v1, double v2) { | |
48 | + return div(v1, v2, DEF_DIV_SCALE); | |
49 | + } | |
50 | + | |
51 | + /** | |
52 | + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度, | |
53 | + * 以后的数字四舍五入。 | |
54 | + */ | |
55 | + public static double div(double v1, double v2, int scale) { | |
56 | + if (scale < 0) { | |
57 | + throw new IllegalArgumentException( | |
58 | + "The scale must be a positive integer or zero"); | |
59 | + } | |
60 | + BigDecimal b1 = new BigDecimal(Double.toString(v1)); | |
61 | + BigDecimal b2 = new BigDecimal(Double.toString(v2)); | |
62 | + if (b1.compareTo(BigDecimal.ZERO) == 0) { | |
63 | + return BigDecimal.ZERO.doubleValue(); | |
64 | + } | |
65 | + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); | |
66 | + } | |
67 | + | |
68 | + /** | |
69 | + * 提供精确的小数位四舍五入处理。 | |
70 | + */ | |
71 | + public static double round(double v, int scale) { | |
72 | + if (scale < 0) { | |
73 | + throw new IllegalArgumentException( | |
74 | + "The scale must be a positive integer or zero"); | |
75 | + } | |
76 | + BigDecimal b = new BigDecimal(Double.toString(v)); | |
77 | + BigDecimal one = new BigDecimal("1"); | |
78 | + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); | |
79 | + } | |
80 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/NumberUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/NumberUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import org.slf4j.Logger; | |
4 | +import org.slf4j.LoggerFactory; | |
5 | + | |
6 | +/** | |
7 | + * 字符串-数值转化工具类 | |
8 | + */ | |
9 | +public class NumberUtils { | |
10 | + private static Logger LOG = LoggerFactory.getLogger(NumberUtils.class); | |
11 | + | |
12 | + public static int str2Int(String number, int defaultValue) { | |
13 | + if (ObjectUtils.isEmpty(number)) { | |
14 | + return defaultValue; | |
15 | + } | |
16 | + | |
17 | + try { | |
18 | + return Integer.parseInt(number); | |
19 | + } catch (NumberFormatException nfe) { | |
20 | + // Never ignore any exception | |
21 | + LOG.error("Invalid number format", nfe); | |
22 | + return defaultValue; | |
23 | + } | |
24 | + } | |
25 | + | |
26 | + public static long str2Long(String number, long defaultValue) { | |
27 | + if (ObjectUtils.isEmpty(number)) { | |
28 | + return defaultValue; | |
29 | + } | |
30 | + | |
31 | + try { | |
32 | + return Long.parseLong(number); | |
33 | + } catch (NumberFormatException nfe) { | |
34 | + // Never ignore any exception | |
35 | + LOG.error("Invalid number format", nfe); | |
36 | + return defaultValue; | |
37 | + } | |
38 | + } | |
39 | + | |
40 | + public static boolean isNumeric(String str) { | |
41 | + if (ObjectUtils.isEmpty(str)) { | |
42 | + return false; | |
43 | + } else { | |
44 | + int sz = str.length(); | |
45 | + | |
46 | + for(int i = 0; i < sz; ++i) { | |
47 | + if (!Character.isDigit(str.charAt(i))) { | |
48 | + return false; | |
49 | + } | |
50 | + } | |
51 | + | |
52 | + return true; | |
53 | + } | |
54 | + } | |
55 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/ObjectUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/ObjectUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.util.ArrayList; | |
4 | +import java.util.List; | |
5 | + | |
6 | +/** | |
7 | + * 通用工具类 | |
8 | + */ | |
9 | +public class ObjectUtils { | |
10 | + public static boolean equals(String str1, String str2) { | |
11 | + if (str1 == str2) { | |
12 | + return true; | |
13 | + } else if (str1 != null && str2 != null) { | |
14 | + if (str1.length() != str2.length()) { | |
15 | + return false; | |
16 | + } else if (str1 instanceof String && str2 instanceof String) { | |
17 | + return str1.equals(str2); | |
18 | + } else { | |
19 | + int length = str1.length(); | |
20 | + | |
21 | + for(int i = 0; i < length; ++i) { | |
22 | + if (str1.charAt(i) != str2.charAt(i)) { | |
23 | + return false; | |
24 | + } | |
25 | + } | |
26 | + | |
27 | + return true; | |
28 | + } | |
29 | + } else { | |
30 | + return false; | |
31 | + } | |
32 | + } | |
33 | + | |
34 | + public static boolean equals(Object object1, Object object2) { | |
35 | + if (object1 == object2) { | |
36 | + return true; | |
37 | + } | |
38 | + return object1 != null && object2 != null ? object1.equals(object2) : false; | |
39 | + } | |
40 | + | |
41 | + public static String[] split(String str, char separator) { | |
42 | + if (str == null) { | |
43 | + return null; | |
44 | + } else { | |
45 | + int len = str.length(); | |
46 | + if (len == 0) { | |
47 | + return new String[0]; | |
48 | + } else { | |
49 | + int i = 0; | |
50 | + int start = 0; | |
51 | + boolean match = false; | |
52 | + boolean lastMatch = false; | |
53 | + boolean preserveAllTokens = false; | |
54 | + List<String> list = new ArrayList<String>(); | |
55 | + | |
56 | + while(true) { | |
57 | + while(i < len) { | |
58 | + if (str.charAt(i) == separator) { | |
59 | + if (match || preserveAllTokens) { | |
60 | + list.add(str.substring(start, i)); | |
61 | + match = false; | |
62 | + lastMatch = true; | |
63 | + } | |
64 | + | |
65 | + ++i; | |
66 | + start = i; | |
67 | + } else { | |
68 | + lastMatch = false; | |
69 | + match = true; | |
70 | + ++i; | |
71 | + } | |
72 | + } | |
73 | + | |
74 | + if (match || preserveAllTokens && lastMatch) { | |
75 | + list.add(str.substring(start, i)); | |
76 | + } | |
77 | + | |
78 | + return (String[])list.toArray(new String[list.size()]); | |
79 | + } | |
80 | + } | |
81 | + } | |
82 | + } | |
83 | + | |
84 | + public static boolean isEmpty(String str) { | |
85 | + return str == null || str.length() == 0; | |
86 | + } | |
87 | + | |
88 | + public static boolean isNotEmpty(String str) { | |
89 | + return !isEmpty(str); | |
90 | + } | |
91 | + | |
92 | + public static <T> boolean isEmpty(List<T> array) { | |
93 | + return array == null || array.isEmpty(); | |
94 | + } | |
95 | + | |
96 | + public static <T> boolean isNotEmpty(List<T> array) { | |
97 | + return array != null && !array.isEmpty(); | |
98 | + } | |
99 | + | |
100 | + public static String trimToEmpty(String str) { | |
101 | + return str == null ? "" : str.trim(); | |
102 | + } | |
103 | + | |
104 | + public static boolean isNull(Object obj) { | |
105 | + return null == obj; | |
106 | + } | |
107 | +} | |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/RandomUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/RandomUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.util.Random; | |
4 | +import java.util.UUID; | |
5 | +import java.util.concurrent.ThreadLocalRandom; | |
6 | + | |
7 | +/** | |
8 | + * 随机数工具类 | |
9 | + */ | |
10 | +public final class RandomUtils { | |
11 | + | |
12 | + /** | |
13 | + * 生成随机字符串, a-z A-Z 0-9 | |
14 | + */ | |
15 | + public static String randomString(int length){ | |
16 | + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |
17 | + Random random = new Random(); | |
18 | + StringBuilder sb = new StringBuilder(); | |
19 | + for(int i = 0; i < length; i++){ | |
20 | + int number = random.nextInt(62); | |
21 | + sb.append(str.charAt(number)); | |
22 | + } | |
23 | + return sb.toString(); | |
24 | + } | |
25 | + | |
26 | + /** | |
27 | + * 生成固定长度的随机数字字符串 | |
28 | + */ | |
29 | + public static String randomNumber(int length) { | |
30 | + AssertUtils.isTrue(length > 0, "invalid length"); | |
31 | + StringBuilder builder = new StringBuilder(); | |
32 | + for (int i = 0; i < length; i ++) { | |
33 | + int next = ThreadLocalRandom.current().nextInt(10); | |
34 | + builder.append((char) (48 + next)); | |
35 | + } | |
36 | + | |
37 | + return builder.toString(); | |
38 | + } | |
39 | + | |
40 | + /** | |
41 | + * 生成8位随机码(数字0~9+大写字母A~Z) | |
42 | + */ | |
43 | + public static String randomCode() { | |
44 | + StringBuilder sb = new StringBuilder(); | |
45 | + for (int i = 0; i < 8; i++) { | |
46 | + int a = Math.abs((new Random()).nextInt(32)); | |
47 | + if (a <= 9) { | |
48 | + sb.append((char) (a + 48)); | |
49 | + } else if (a < 33) { | |
50 | + if ((a + 55) == 79 || (a + 55) == 73) { | |
51 | + sb.append((char) (a + 63)); | |
52 | + } else { | |
53 | + sb.append((char) (a + 55)); | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + return sb.toString(); | |
58 | + } | |
59 | + | |
60 | + | |
61 | + /** | |
62 | + * 功能描述:获取随机字符串 | |
63 | + */ | |
64 | + public static String randomString(int length, String original) { | |
65 | + if (length <= 0 || ObjectUtils.isEmpty(original)) { | |
66 | + return null; | |
67 | + } | |
68 | + StringBuilder sb = new StringBuilder(); | |
69 | + int len = original.length(); | |
70 | + for (int i = 0; i < length; i++) { | |
71 | + int round = (int) Math.round(Math.random() * (len - 1)); | |
72 | + sb.append(original.charAt(round)); | |
73 | + } | |
74 | + return sb.toString(); | |
75 | + } | |
76 | + | |
77 | + /** | |
78 | + * UUID随机数,移除 "-" | |
79 | + */ | |
80 | + public static String randomUUID() { | |
81 | + return randomUUID(true); | |
82 | + } | |
83 | + | |
84 | + public static String randomUUID(boolean upperCase) { | |
85 | + UUID uuid = UUID.randomUUID(); | |
86 | + long mostSigBits = uuid.getMostSignificantBits(); | |
87 | + long leastSigBits = uuid.getLeastSignificantBits(); | |
88 | + return (digits(mostSigBits >> 32, 8, upperCase) + | |
89 | + digits(mostSigBits >> 16, 4, upperCase) + | |
90 | + digits(mostSigBits, 4, upperCase) + | |
91 | + digits(leastSigBits >> 48, 4, upperCase) + | |
92 | + digits(leastSigBits, 12, upperCase)); | |
93 | + } | |
94 | + | |
95 | + public static String randomCaptcha() { | |
96 | + return String.valueOf((int) ((Math.random() * ((1 << 3) + 1) + 1) * 1000)); | |
97 | + } | |
98 | + | |
99 | + /** | |
100 | + * 生成随机区间数字 | |
101 | + */ | |
102 | + public static int randomInt(int min, int max) { | |
103 | + return min + (int)(Math.random() * (max + 1 - min)); | |
104 | + } | |
105 | + | |
106 | + private static String digits(long val, int digits, boolean upperCase) { | |
107 | + long hi = 1L << (digits * 4); | |
108 | + String hexDigits = Long.toHexString(hi | (val & (hi - 1))).substring(1); | |
109 | + return upperCase ? hexDigits.toUpperCase() : hexDigits; | |
110 | + } | |
111 | +} | |
0 | 112 | \ No newline at end of file |
... | ... |
boss-shared/src/main/java/com/diligrp/boss/shared/util/ReflectUtils.java
0 → 100644
1 | +++ a/boss-shared/src/main/java/com/diligrp/boss/shared/util/ReflectUtils.java | |
1 | +package com.diligrp.boss.shared.util; | |
2 | + | |
3 | +import java.lang.reflect.Field; | |
4 | +import java.lang.reflect.InvocationTargetException; | |
5 | +import java.lang.reflect.Method; | |
6 | +import java.util.Arrays; | |
7 | + | |
8 | +/** | |
9 | + * 利用反射进行操作的一个工具类 | |
10 | + */ | |
11 | +public class ReflectUtils { | |
12 | + /** | |
13 | + * 利用反射获取指定对象的指定属性 | |
14 | + */ | |
15 | + public static Object getFieldValue(Object target, String fieldName) { | |
16 | + Object result = null; | |
17 | + Field field = ReflectUtils.getField(target, fieldName); | |
18 | + if (field != null) { | |
19 | + field.setAccessible(true); | |
20 | + try { | |
21 | + result = field.get(target); | |
22 | + } catch (IllegalArgumentException | IllegalAccessException ex) { | |
23 | + throw new RuntimeException("Illegal access or argument exception", ex); | |
24 | + } | |
25 | + } | |
26 | + return result; | |
27 | + } | |
28 | + | |
29 | + /** | |
30 | + * 利用反射获取指定对象里面的指定属性 | |
31 | + */ | |
32 | + private static Field getField(Object obj, String fieldName) { | |
33 | + Field field = null; | |
34 | + for (Class<?> clazz = obj.getClass(); | |
35 | + clazz != Object.class; clazz = clazz.getSuperclass()) { | |
36 | + try { | |
37 | + field = clazz.getDeclaredField(fieldName); | |
38 | + break; | |
39 | + } catch (NoSuchFieldException e) { | |
40 | + // 当前类没有此方法则向父类查找,都没有就返回NULL | |
41 | + } | |
42 | + } | |
43 | + return field; | |
44 | + } | |
45 | + | |
46 | + /** | |
47 | + * 利用反射设置指定对象的指定属性为指定的值 | |
48 | + */ | |
49 | + public static void setFieldValue(Object obj, String fieldName, String fieldValue) { | |
50 | + Field field = ReflectUtils.getField(obj, fieldName); | |
51 | + if (field != null) { | |
52 | + try { | |
53 | + field.setAccessible(true); | |
54 | + field.set(obj, fieldValue); | |
55 | + } catch (IllegalArgumentException | IllegalAccessException ex) { | |
56 | + throw new RuntimeException("Illegal access or argument exception", ex); | |
57 | + } | |
58 | + } | |
59 | + } | |
60 | + | |
61 | + /** | |
62 | + * 调用对象方法, 包含private/protected修饰的方法. | |
63 | + */ | |
64 | + public static Object invokeMethod(final Object target, final String methodName, final Class<?>[] parameterTypes, | |
65 | + final Object[] parameters) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { | |
66 | + | |
67 | + Method method = getDeclaredMethod(target, methodName, parameterTypes); | |
68 | + if (method == null) { | |
69 | + throw new IllegalArgumentException("Could not find method [" | |
70 | + + methodName + "] parameterType " + Arrays.toString(parameterTypes) | |
71 | + + " on target [" + target + "]"); | |
72 | + } | |
73 | + method.setAccessible(true); | |
74 | + return method.invoke(target, parameters); | |
75 | + } | |
76 | + | |
77 | + /** | |
78 | + * 在target对象上查找方法,如果当前类定义未定义则向父类查找,都未查找到则返回Null. | |
79 | + */ | |
80 | + protected static Method getDeclaredMethod(Object target, String methodName, Class<?>[] parameterTypes) { | |
81 | + AssertUtils.notNull(target, "target must be not null"); | |
82 | + for (Class<?> superClass = target.getClass(); superClass != Object.class; | |
83 | + superClass = superClass.getSuperclass()) { | |
84 | + try { | |
85 | + return superClass.getDeclaredMethod(methodName, parameterTypes); | |
86 | + } catch (NoSuchMethodException sme) { | |
87 | + // 当前类未定义Method则向父类查找 | |
88 | + } | |
89 | + } | |
90 | + return null; | |
91 | + } | |
92 | +} | |
0 | 93 | \ No newline at end of file |
... | ... |
boss-shared/src/main/resources/com/diligrp/boss/dao/mapper/SequenceKeyDao.xml
0 → 100644
1 | +++ a/boss-shared/src/main/resources/com/diligrp/boss/dao/mapper/SequenceKeyDao.xml | |
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" | |
3 | + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
4 | + | |
5 | +<mapper namespace="com.diligrp.boss.shared.dao.SequenceKeyDao"> | |
6 | + <resultMap id="PersistentKeyMap" type="com.diligrp.boss.shared.domain.PersistentSequenceKey"> | |
7 | + <id column="id" property="id"/> | |
8 | + <result column="key" property="key"/> | |
9 | + <result column="name" property="name"/> | |
10 | + <result column="value" property="value"/> | |
11 | + <result column="step" property="step"/> | |
12 | + <result column="pattern" property="pattern"/> | |
13 | + <result column="expired_on" property="expiredOn"/> | |
14 | + <result column="today" property="today"/> | |
15 | + <result column="version" property="version"/> | |
16 | + </resultMap> | |
17 | + | |
18 | + <insert id="insertSequenceKey" parameterType="com.diligrp.boss.shared.domain.PersistentSequenceKey"> | |
19 | + INSERT INTO uid_sequence_key(`key`, name, value, step, pattern, expired_on, version) | |
20 | + VALUES (#{key}, #{name}, #{value}, #{step}, #{pattern}, #{expiredOn}, #{version}) | |
21 | + </insert> | |
22 | + | |
23 | + <select id="findSequenceKey" parameterType="string" resultMap="PersistentKeyMap"> | |
24 | + SELECT | |
25 | + id, `key`, name, value, step, pattern, expired_on, curdate() AS today, version | |
26 | + FROM | |
27 | + uid_sequence_key | |
28 | + WHERE | |
29 | + `key` = #{key} | |
30 | + </select> | |
31 | + | |
32 | + <select id="findSequenceKeyById" parameterType="long" resultMap="PersistentKeyMap"> | |
33 | + SELECT | |
34 | + id, `key`, name, value, step, pattern, expired_on, curdate() AS today, version | |
35 | + FROM | |
36 | + uid_sequence_key | |
37 | + WHERE | |
38 | + id = #{id} | |
39 | + </select> | |
40 | + | |
41 | + <select id="lockSequenceKey" parameterType="long" resultMap="PersistentKeyMap"> | |
42 | + SELECT | |
43 | + id, `key`, name, value, step, pattern, expired_on, curdate() AS today, version | |
44 | + FROM | |
45 | + uid_sequence_key | |
46 | + WHERE | |
47 | + id = #{id} | |
48 | + FOR UPDATE | |
49 | + </select> | |
50 | + | |
51 | + <update id="unlockSequenceKey" parameterType="com.diligrp.boss.shared.domain.PersistentSequenceKey"> | |
52 | + UPDATE | |
53 | + uid_sequence_key | |
54 | + SET | |
55 | + value = #{value}, expired_on = #{expiredOn}, version = version + 1 | |
56 | + WHERE | |
57 | + id = #{id} | |
58 | + </update> | |
59 | +</mapper> | |
0 | 60 | \ No newline at end of file |
... | ... |
boss-support/build.gradle
0 → 100644
boss-support/src/main/java/com/diligrp/boss/support/SupportConfiguration.java
0 → 100644
1 | +++ a/boss-support/src/main/java/com/diligrp/boss/support/SupportConfiguration.java | |
1 | +package com.diligrp.boss.support; | |
2 | + | |
3 | +import com.diligrp.boss.shared.mybatis.MybatisMapperSupport; | |
4 | +import org.mybatis.spring.annotation.MapperScan; | |
5 | +import org.springframework.context.annotation.ComponentScan; | |
6 | +import org.springframework.context.annotation.Configuration; | |
7 | + | |
8 | +@Configuration | |
9 | +@ComponentScan("com.diligrp.boss.support") | |
10 | +@MapperScan(basePackages = {"com.diligrp.boss.support.dao"}, markerInterface = MybatisMapperSupport.class) | |
11 | +public class SupportConfiguration { | |
12 | +} | |
... | ... |
build.gradle
0 → 100644
1 | +++ a/build.gradle | |
1 | +plugins { | |
2 | + id 'java' | |
3 | + id 'org.springframework.boot' version '3.1.2' | |
4 | + id 'io.spring.dependency-management' version '1.1.2' | |
5 | +} | |
6 | + | |
7 | +jar.enabled = false | |
8 | +bootJar.enabled = false | |
9 | + | |
10 | +allprojects { | |
11 | + repositories { | |
12 | + mavenLocal() | |
13 | + maven { | |
14 | + url 'https://maven.aliyun.com/nexus/content/groups/public/' | |
15 | + } | |
16 | + maven { | |
17 | + allowInsecureProtocol = true | |
18 | + // credentials | |
19 | + credentials { | |
20 | + username 'admin' | |
21 | + password 'for211314' | |
22 | + } | |
23 | + url = 'http://mvn2.diligrp.com/artifactory/libs-snapshot/' | |
24 | + } | |
25 | + mavenCentral() | |
26 | + } | |
27 | +} | |
28 | + | |
29 | +subprojects { | |
30 | + apply plugin: 'java' | |
31 | + apply plugin: 'java-library' | |
32 | + apply plugin: 'org.springframework.boot' | |
33 | + apply plugin: 'io.spring.dependency-management' | |
34 | + | |
35 | + version = '1.0.0' | |
36 | + | |
37 | + java { | |
38 | + sourceCompatibility = JavaVersion.VERSION_17 | |
39 | + targetCompatibility = JavaVersion.VERSION_17 | |
40 | + } | |
41 | + | |
42 | + ext { | |
43 | + set('springCloudVersion', "2022.0.4") | |
44 | + } | |
45 | + | |
46 | + jar.archiveClassifier = '' | |
47 | + bootJar.enabled = false | |
48 | + | |
49 | + dependencies { | |
50 | + implementation 'org.springframework.cloud:spring-cloud-starter' | |
51 | + implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' | |
52 | + implementation 'org.springframework.boot:spring-boot-starter-web' | |
53 | + implementation fileTree(dir: "$rootProject.projectDir/libs", includes: ['*jar']) | |
54 | + } | |
55 | + | |
56 | + dependencyManagement { | |
57 | + imports { | |
58 | + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" | |
59 | + } | |
60 | + } | |
61 | +} | |
0 | 62 | \ No newline at end of file |
... | ... |
gradle/wrapper/gradle-wrapper.jar
0 → 100644
No preview for this file type
gradle/wrapper/gradle-wrapper.properties
0 → 100644
1 | +++ a/gradle/wrapper/gradle-wrapper.properties | |
1 | +distributionBase=GRADLE_USER_HOME | |
2 | +distributionPath=wrapper/dists | |
3 | +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip | |
4 | +networkTimeout=10000 | |
5 | +validateDistributionUrl=true | |
6 | +zipStoreBase=GRADLE_USER_HOME | |
7 | +zipStorePath=wrapper/dists | |
... | ... |
gradlew
0 → 100644
1 | +++ a/gradlew | |
1 | +#!/bin/sh | |
2 | + | |
3 | +# | |
4 | +# Copyright © 2015-2021 the original authors. | |
5 | +# | |
6 | +# Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | +# you may not use this file except in compliance with the License. | |
8 | +# You may obtain a copy of the License at | |
9 | +# | |
10 | +# https://www.apache.org/licenses/LICENSE-2.0 | |
11 | +# | |
12 | +# Unless required by applicable law or agreed to in writing, software | |
13 | +# distributed under the License is distributed on an "AS IS" BASIS, | |
14 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | +# See the License for the specific language governing permissions and | |
16 | +# limitations under the License. | |
17 | +# | |
18 | + | |
19 | +############################################################################## | |
20 | +# | |
21 | +# Gradle start up script for POSIX generated by Gradle. | |
22 | +# | |
23 | +# Important for running: | |
24 | +# | |
25 | +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | |
26 | +# noncompliant, but you have some other compliant shell such as ksh or | |
27 | +# bash, then to run this script, type that shell name before the whole | |
28 | +# command line, like: | |
29 | +# | |
30 | +# ksh Gradle | |
31 | +# | |
32 | +# Busybox and similar reduced shells will NOT work, because this script | |
33 | +# requires all of these POSIX shell features: | |
34 | +# * functions; | |
35 | +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | |
36 | +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; | |
37 | +# * compound commands having a testable exit status, especially «case»; | |
38 | +# * various built-in commands including «command», «set», and «ulimit». | |
39 | +# | |
40 | +# Important for patching: | |
41 | +# | |
42 | +# (2) This script targets any POSIX shell, so it avoids extensions provided | |
43 | +# by Bash, Ksh, etc; in particular arrays are avoided. | |
44 | +# | |
45 | +# The "traditional" practice of packing multiple parameters into a | |
46 | +# space-separated string is a well documented source of bugs and security | |
47 | +# problems, so this is (mostly) avoided, by progressively accumulating | |
48 | +# options in "$@", and eventually passing that to Java. | |
49 | +# | |
50 | +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | |
51 | +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | |
52 | +# see the in-line comments for details. | |
53 | +# | |
54 | +# There are tweaks for specific operating systems such as AIX, CygWin, | |
55 | +# Darwin, MinGW, and NonStop. | |
56 | +# | |
57 | +# (3) This script is generated from the Groovy template | |
58 | +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | |
59 | +# within the Gradle project. | |
60 | +# | |
61 | +# You can find Gradle at https://github.com/gradle/gradle/. | |
62 | +# | |
63 | +############################################################################## | |
64 | + | |
65 | +# Attempt to set APP_HOME | |
66 | + | |
67 | +# Resolve links: $0 may be a link | |
68 | +app_path=$0 | |
69 | + | |
70 | +# Need this for daisy-chained symlinks. | |
71 | +while | |
72 | + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path | |
73 | + [ -h "$app_path" ] | |
74 | +do | |
75 | + ls=$( ls -ld "$app_path" ) | |
76 | + link=${ls#*' -> '} | |
77 | + case $link in #( | |
78 | + /*) app_path=$link ;; #( | |
79 | + *) app_path=$APP_HOME$link ;; | |
80 | + esac | |
81 | +done | |
82 | + | |
83 | +# This is normally unused | |
84 | +# shellcheck disable=SC2034 | |
85 | +APP_BASE_NAME=${0##*/} | |
86 | +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit | |
87 | + | |
88 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | |
89 | +MAX_FD=maximum | |
90 | + | |
91 | +warn () { | |
92 | + echo "$*" | |
93 | +} >&2 | |
94 | + | |
95 | +die () { | |
96 | + echo | |
97 | + echo "$*" | |
98 | + echo | |
99 | + exit 1 | |
100 | +} >&2 | |
101 | + | |
102 | +# OS specific support (must be 'true' or 'false'). | |
103 | +cygwin=false | |
104 | +msys=false | |
105 | +darwin=false | |
106 | +nonstop=false | |
107 | +case "$( uname )" in #( | |
108 | + CYGWIN* ) cygwin=true ;; #( | |
109 | + Darwin* ) darwin=true ;; #( | |
110 | + MSYS* | MINGW* ) msys=true ;; #( | |
111 | + NONSTOP* ) nonstop=true ;; | |
112 | +esac | |
113 | + | |
114 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |
115 | + | |
116 | + | |
117 | +# Determine the Java command to use to start the JVM. | |
118 | +if [ -n "$JAVA_HOME" ] ; then | |
119 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |
120 | + # IBM's JDK on AIX uses strange locations for the executables | |
121 | + JAVACMD=$JAVA_HOME/jre/sh/java | |
122 | + else | |
123 | + JAVACMD=$JAVA_HOME/bin/java | |
124 | + fi | |
125 | + if [ ! -x "$JAVACMD" ] ; then | |
126 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |
127 | + | |
128 | +Please set the JAVA_HOME variable in your environment to match the | |
129 | +location of your Java installation." | |
130 | + fi | |
131 | +else | |
132 | + JAVACMD=java | |
133 | + if ! command -v java >/dev/null 2>&1 | |
134 | + then | |
135 | + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
136 | + | |
137 | +Please set the JAVA_HOME variable in your environment to match the | |
138 | +location of your Java installation." | |
139 | + fi | |
140 | +fi | |
141 | + | |
142 | +# Increase the maximum file descriptors if we can. | |
143 | +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | |
144 | + case $MAX_FD in #( | |
145 | + max*) | |
146 | + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. | |
147 | + # shellcheck disable=SC3045 | |
148 | + MAX_FD=$( ulimit -H -n ) || | |
149 | + warn "Could not query maximum file descriptor limit" | |
150 | + esac | |
151 | + case $MAX_FD in #( | |
152 | + '' | soft) :;; #( | |
153 | + *) | |
154 | + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. | |
155 | + # shellcheck disable=SC3045 | |
156 | + ulimit -n "$MAX_FD" || | |
157 | + warn "Could not set maximum file descriptor limit to $MAX_FD" | |
158 | + esac | |
159 | +fi | |
160 | + | |
161 | +# Collect all arguments for the java command, stacking in reverse order: | |
162 | +# * args from the command line | |
163 | +# * the main class name | |
164 | +# * -classpath | |
165 | +# * -D...appname settings | |
166 | +# * --module-path (only if needed) | |
167 | +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | |
168 | + | |
169 | +# For Cygwin or MSYS, switch paths to Windows format before running java | |
170 | +if "$cygwin" || "$msys" ; then | |
171 | + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | |
172 | + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | |
173 | + | |
174 | + JAVACMD=$( cygpath --unix "$JAVACMD" ) | |
175 | + | |
176 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | |
177 | + for arg do | |
178 | + if | |
179 | + case $arg in #( | |
180 | + -*) false ;; # don't mess with options #( | |
181 | + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath | |
182 | + [ -e "$t" ] ;; #( | |
183 | + *) false ;; | |
184 | + esac | |
185 | + then | |
186 | + arg=$( cygpath --path --ignore --mixed "$arg" ) | |
187 | + fi | |
188 | + # Roll the args list around exactly as many times as the number of | |
189 | + # args, so each arg winds up back in the position where it started, but | |
190 | + # possibly modified. | |
191 | + # | |
192 | + # NB: a `for` loop captures its iteration list before it begins, so | |
193 | + # changing the positional parameters here affects neither the number of | |
194 | + # iterations, nor the values presented in `arg`. | |
195 | + shift # remove old arg | |
196 | + set -- "$@" "$arg" # push replacement arg | |
197 | + done | |
198 | +fi | |
199 | + | |
200 | + | |
201 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
202 | +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | |
203 | + | |
204 | +# Collect all arguments for the java command; | |
205 | +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of | |
206 | +# shell script including quotes and variable substitutions, so put them in | |
207 | +# double quotes to make sure that they get re-expanded; and | |
208 | +# * put everything else in single quotes, so that it's not re-expanded. | |
209 | + | |
210 | +set -- \ | |
211 | + "-Dorg.gradle.appname=$APP_BASE_NAME" \ | |
212 | + -classpath "$CLASSPATH" \ | |
213 | + org.gradle.wrapper.GradleWrapperMain \ | |
214 | + "$@" | |
215 | + | |
216 | +# Stop when "xargs" is not available. | |
217 | +if ! command -v xargs >/dev/null 2>&1 | |
218 | +then | |
219 | + die "xargs is not available" | |
220 | +fi | |
221 | + | |
222 | +# Use "xargs" to parse quoted args. | |
223 | +# | |
224 | +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. | |
225 | +# | |
226 | +# In Bash we could simply go: | |
227 | +# | |
228 | +# readarray ARGS < <( xargs -n1 <<<"$var" ) && | |
229 | +# set -- "${ARGS[@]}" "$@" | |
230 | +# | |
231 | +# but POSIX shell has neither arrays nor command substitution, so instead we | |
232 | +# post-process each arg (as a line of input to sed) to backslash-escape any | |
233 | +# character that might be a shell metacharacter, then use eval to reverse | |
234 | +# that process (while maintaining the separation between arguments), and wrap | |
235 | +# the whole thing up as a single "set" statement. | |
236 | +# | |
237 | +# This will of course break if any of these variables contains a newline or | |
238 | +# an unmatched quote. | |
239 | +# | |
240 | + | |
241 | +eval "set -- $( | |
242 | + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | |
243 | + xargs -n1 | | |
244 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | |
245 | + tr '\n' ' ' | |
246 | + )" '"$@"' | |
247 | + | |
248 | +exec "$JAVACMD" "$@" | |
... | ... |
gradlew.bat
0 → 100644
1 | +++ a/gradlew.bat | |
1 | +@rem | |
2 | +@rem Copyright 2015 the original author or authors. | |
3 | +@rem | |
4 | +@rem Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +@rem you may not use this file except in compliance with the License. | |
6 | +@rem You may obtain a copy of the License at | |
7 | +@rem | |
8 | +@rem https://www.apache.org/licenses/LICENSE-2.0 | |
9 | +@rem | |
10 | +@rem Unless required by applicable law or agreed to in writing, software | |
11 | +@rem distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +@rem See the License for the specific language governing permissions and | |
14 | +@rem limitations under the License. | |
15 | +@rem | |
16 | + | |
17 | +@if "%DEBUG%"=="" @echo off | |
18 | +@rem ########################################################################## | |
19 | +@rem | |
20 | +@rem Gradle startup script for Windows | |
21 | +@rem | |
22 | +@rem ########################################################################## | |
23 | + | |
24 | +@rem Set local scope for the variables with windows NT shell | |
25 | +if "%OS%"=="Windows_NT" setlocal | |
26 | + | |
27 | +set DIRNAME=%~dp0 | |
28 | +if "%DIRNAME%"=="" set DIRNAME=. | |
29 | +@rem This is normally unused | |
30 | +set APP_BASE_NAME=%~n0 | |
31 | +set APP_HOME=%DIRNAME% | |
32 | + | |
33 | +@rem Resolve any "." and ".." in APP_HOME to make it shorter. | |
34 | +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | |
35 | + | |
36 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
37 | +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | |
38 | + | |
39 | +@rem Find java.exe | |
40 | +if defined JAVA_HOME goto findJavaFromJavaHome | |
41 | + | |
42 | +set JAVA_EXE=java.exe | |
43 | +%JAVA_EXE% -version >NUL 2>&1 | |
44 | +if %ERRORLEVEL% equ 0 goto execute | |
45 | + | |
46 | +echo. | |
47 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
48 | +echo. | |
49 | +echo Please set the JAVA_HOME variable in your environment to match the | |
50 | +echo location of your Java installation. | |
51 | + | |
52 | +goto fail | |
53 | + | |
54 | +:findJavaFromJavaHome | |
55 | +set JAVA_HOME=%JAVA_HOME:"=% | |
56 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |
57 | + | |
58 | +if exist "%JAVA_EXE%" goto execute | |
59 | + | |
60 | +echo. | |
61 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |
62 | +echo. | |
63 | +echo Please set the JAVA_HOME variable in your environment to match the | |
64 | +echo location of your Java installation. | |
65 | + | |
66 | +goto fail | |
67 | + | |
68 | +:execute | |
69 | +@rem Setup the command line | |
70 | + | |
71 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |
72 | + | |
73 | + | |
74 | +@rem Execute Gradle | |
75 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | |
76 | + | |
77 | +:end | |
78 | +@rem End local scope for the variables with windows NT shell | |
79 | +if %ERRORLEVEL% equ 0 goto mainEnd | |
80 | + | |
81 | +:fail | |
82 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |
83 | +rem the _cmd.exe /c_ return code! | |
84 | +set EXIT_CODE=%ERRORLEVEL% | |
85 | +if %EXIT_CODE% equ 0 set EXIT_CODE=1 | |
86 | +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% | |
87 | +exit /b %EXIT_CODE% | |
88 | + | |
89 | +:mainEnd | |
90 | +if "%OS%"=="Windows_NT" endlocal | |
91 | + | |
92 | +:omega | |
... | ... |
scripts/boss-data.sql
0 → 100644
scripts/dili-boss.sql
0 → 100644
1 | +++ a/scripts/dili-boss.sql | |
1 | +USE dili_boss; | |
2 | + | |
3 | +-- -------------------------------------------------------------------- | |
4 | +-- 系统ID生成器数据模型 | |
5 | +-- -------------------------------------------------------------------- | |
6 | +DROP TABLE IF EXISTS `uid_sequence_key`; | |
7 | +CREATE TABLE `uid_sequence_key` ( | |
8 | + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', | |
9 | + `KEY` VARCHAR(40) NOT NULL COMMENT 'KEY标识', | |
10 | + `name` VARCHAR(80) NOT NULL COMMENT 'KEY名称', | |
11 | + `value` BIGINT NOT NULl COMMENT '起始值', | |
12 | + `step` TINYINT UNSIGNED NOT NULL COMMENT '步长', | |
13 | + `pattern` VARCHAR(60) COMMENT 'ID格式', -- ORDER-%d{yyyyMMdd}-%n{5}-%r{1} | |
14 | + `expired_on` DATE COMMENT '有效日期', | |
15 | + `version` BIGINT NOT NULL COMMENT '数据版本', | |
16 | + PRIMARY KEY (`id`), | |
17 | + UNIQUE KEY `uk_sequence_key_key` (`KEY`) USING BTREE | |
18 | +) ENGINE=InnoDB; | |
0 | 19 | \ No newline at end of file |
... | ... |