Commit 7976759a81cb1a6fdfa80a3ad7525a65a9024442
0 parents
dili cashier project init
Showing
113 changed files
with
6367 additions
and
0 deletions
.gitignore
0 → 100644
build.gradle
0 → 100644
| 1 | +++ a/build.gradle | ||
| 1 | +plugins { | ||
| 2 | + // id 'org.springframework.boot' version '4.0.0' apply false | ||
| 3 | + // id 'io.spring.dependency-management' version '1.1.7' apply false | ||
| 4 | + alias(libs.plugins.spring.boot) apply false | ||
| 5 | + alias(libs.plugins.spring.dependency.management) apply false | ||
| 6 | + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +allprojects { | ||
| 10 | + group = 'com.diligrp' | ||
| 11 | +// version = "${libs.versions.appVersion.get()}" | ||
| 12 | + version = libs.versions.appVersion.get() | ||
| 13 | + | ||
| 14 | + repositories { | ||
| 15 | + mavenLocal() | ||
| 16 | + maven { | ||
| 17 | + url 'https://maven.aliyun.com/repository/central' | ||
| 18 | + } | ||
| 19 | + maven { | ||
| 20 | + url 'https://maven.aliyun.com/repository/public' | ||
| 21 | + } | ||
| 22 | + maven { | ||
| 23 | + url 'https://maven.aliyun.com/repository/gradle-plugin' | ||
| 24 | + } | ||
| 25 | + mavenCentral() | ||
| 26 | + } | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +subprojects { | ||
| 30 | + apply plugin: 'java' | ||
| 31 | + apply plugin: 'java-library' | ||
| 32 | + apply plugin: 'io.spring.dependency-management' | ||
| 33 | + | ||
| 34 | + java { | ||
| 35 | + sourceCompatibility = JavaVersion.VERSION_21 | ||
| 36 | + targetCompatibility = JavaVersion.VERSION_21 | ||
| 37 | + // 可选:启用模块系统(JPMS) | ||
| 38 | + modularity.inferModulePath = true | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + dependencies { | ||
| 42 | + implementation libs.spring.boot.starter.web | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + dependencyManagement { | ||
| 46 | + imports { | ||
| 47 | + mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES | ||
| 48 | + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${libs.versions.springCloudVersion.get()}" | ||
| 49 | + } | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + tasks.withType(JavaCompile) { | ||
| 53 | + options.encoding = 'UTF-8' | ||
| 54 | + options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation'] | ||
| 55 | + } | ||
| 56 | +} | ||
| 0 | \ No newline at end of file | 57 | \ No newline at end of file |
cashier-assistant/build.gradle
0 → 100644
| 1 | +++ a/cashier-assistant/build.gradle | ||
| 1 | +dependencies { | ||
| 2 | + api project(':cashier-shared') | ||
| 3 | + implementation 'org.apache.commons:commons-text:1.15.0' | ||
| 4 | + implementation('com.aliyun:dysmsapi20170525:4.3.1') { | ||
| 5 | + // 排除指定模块, 否则将导致logback初始化时xml parse异常 | ||
| 6 | + exclude group: 'pull-parser', module: 'pull-parser' | ||
| 7 | + } | ||
| 8 | +} | ||
| 0 | \ No newline at end of file | 9 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/AssistantConfiguration.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/AssistantConfiguration.java | ||
| 1 | +package com.diligrp.cashier.assistant; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.pipeline.*; | ||
| 4 | +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; | ||
| 5 | +import org.mybatis.spring.annotation.MapperScan; | ||
| 6 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 7 | +import org.springframework.context.annotation.Bean; | ||
| 8 | +import org.springframework.context.annotation.ComponentScan; | ||
| 9 | +import org.springframework.context.annotation.Configuration; | ||
| 10 | + | ||
| 11 | +@Configuration | ||
| 12 | +@ComponentScan("com.diligrp.cashier.assistant") | ||
| 13 | +@MapperScan(basePackages = {"com.diligrp.cashier.assistant.dao"}, markerInterface = MybatisMapperSupport.class) | ||
| 14 | +public class AssistantConfiguration { | ||
| 15 | + | ||
| 16 | + @Bean | ||
| 17 | + @ConfigurationProperties("sms") | ||
| 18 | + public SmsProperties smsProperties() { | ||
| 19 | + return new SmsProperties(); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Bean | ||
| 23 | + public SmsPipelineManager smsPipelineManager(SmsProperties properties) { | ||
| 24 | + SmsPipelineManager pipelineManager = new DefaultSmsPipelineManager(); | ||
| 25 | + | ||
| 26 | + // TODO: 可利用数据库进行通道配置, 前期并没有必要 | ||
| 27 | + SmsProperties.AliSms aliSms = properties.getAlisms(); | ||
| 28 | + if (aliSms != null) { | ||
| 29 | + SmsPipeline aliPipeline = new AliSmsPipeline(10, "阿里云短信服务通道", aliSms.getEndPoint(), | ||
| 30 | + aliSms.getAccessKeyId(), aliSms.getAccessKeySecret()); | ||
| 31 | + pipelineManager.registerPipeline(aliPipeline); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + SmsProperties.SmsChinese chinese = properties.getSmschinese(); | ||
| 35 | + if (chinese != null) { | ||
| 36 | + SmsPipeline pipeline = new SmsChinesePipeline(20, "网建短信服务通道", chinese.getUri(), | ||
| 37 | + chinese.getUid(), chinese.getSecretKey()); | ||
| 38 | + pipelineManager.registerPipeline(pipeline); | ||
| 39 | + } | ||
| 40 | + return pipelineManager; | ||
| 41 | + } | ||
| 42 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/SmsProperties.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/SmsProperties.java | ||
| 1 | +package com.diligrp.cashier.assistant; | ||
| 2 | + | ||
| 3 | +import java.security.PrivateKey; | ||
| 4 | +import java.security.PublicKey; | ||
| 5 | + | ||
| 6 | +public class SmsProperties { | ||
| 7 | + // 私钥 | ||
| 8 | + private PrivateKey privateKey; | ||
| 9 | + // 公钥 | ||
| 10 | + private PublicKey publicKey; | ||
| 11 | + // 阿里云短信服务通道 | ||
| 12 | + private AliSms alisms; | ||
| 13 | + // 网建短信服务通道 | ||
| 14 | + private SmsChinese smschinese; | ||
| 15 | + | ||
| 16 | + public PrivateKey getPrivateKey() { | ||
| 17 | + return privateKey; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public void setPrivateKey(PrivateKey privateKey) { | ||
| 21 | + this.privateKey = privateKey; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public PublicKey getPublicKey() { | ||
| 25 | + return publicKey; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public void setPublicKey(PublicKey publicKey) { | ||
| 29 | + this.publicKey = publicKey; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public AliSms getAlisms() { | ||
| 33 | + return alisms; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public void setAlisms(AliSms alisms) { | ||
| 37 | + this.alisms = alisms; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public SmsChinese getSmschinese() { | ||
| 41 | + return smschinese; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public void setSmschinese(SmsChinese smschinese) { | ||
| 45 | + this.smschinese = smschinese; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public static class AliSms { | ||
| 49 | + private String endPoint; | ||
| 50 | + private String accessKeyId; | ||
| 51 | + private String accessKeySecret; | ||
| 52 | + | ||
| 53 | + public String getEndPoint() { | ||
| 54 | + return endPoint; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public void setEndPoint(String endPoint) { | ||
| 58 | + this.endPoint = endPoint; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + public String getAccessKeyId() { | ||
| 62 | + return accessKeyId; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public void setAccessKeyId(String accessKeyId) { | ||
| 66 | + this.accessKeyId = accessKeyId; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public String getAccessKeySecret() { | ||
| 70 | + return accessKeySecret; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public void setAccessKeySecret(String accessKeySecret) { | ||
| 74 | + this.accessKeySecret = accessKeySecret; | ||
| 75 | + } | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public static class SmsChinese { | ||
| 79 | + private String uri; | ||
| 80 | + private String uid; | ||
| 81 | + private String secretKey; | ||
| 82 | + | ||
| 83 | + public String getUri() { | ||
| 84 | + return uri; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + public void setUri(String uri) { | ||
| 88 | + this.uri = uri; | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + public String getUid() { | ||
| 92 | + return uid; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + public void setUid(String uid) { | ||
| 96 | + this.uid = uid; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + public String getSecretKey() { | ||
| 100 | + return secretKey; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + public void setSecretKey(String secretKey) { | ||
| 104 | + this.secretKey = secretKey; | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | +} | ||
| 0 | \ No newline at end of file | 108 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/client/AliSmsHttpClient.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/client/AliSmsHttpClient.java | ||
| 1 | +package com.diligrp.cashier.assistant.client; | ||
| 2 | + | ||
| 3 | +import com.aliyun.dysmsapi20170525.Client; | ||
| 4 | +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; | ||
| 5 | +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; | ||
| 6 | +import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody; | ||
| 7 | +import com.aliyun.teaopenapi.models.Config; | ||
| 8 | +import com.diligrp.cashier.assistant.domain.SmsMessage; | ||
| 9 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 10 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 11 | +import org.slf4j.Logger; | ||
| 12 | +import org.slf4j.LoggerFactory; | ||
| 13 | + | ||
| 14 | +import java.util.Objects; | ||
| 15 | +import java.util.stream.Collectors; | ||
| 16 | + | ||
| 17 | +public class AliSmsHttpClient { | ||
| 18 | + | ||
| 19 | + private static final Logger LOGGER = LoggerFactory.getLogger(AliSmsHttpClient.class); | ||
| 20 | + | ||
| 21 | + private final Client client; | ||
| 22 | + | ||
| 23 | + public AliSmsHttpClient(String endPoint, String accessKeyId, String accessKeySecret) { | ||
| 24 | + Config config = new Config().setAccessKeyId(accessKeyId).setAccessKeySecret(accessKeySecret); | ||
| 25 | + config.endpoint = endPoint; | ||
| 26 | + try { | ||
| 27 | + this.client = new Client(config); | ||
| 28 | + } catch (Exception e) { | ||
| 29 | + throw new AssistantServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "阿里云短信通道初始化错误"); | ||
| 30 | + } | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public String sendSmsMessage(SmsMessage message) { | ||
| 34 | + String telephones = message.getTelephones().stream().reduce((telephone1, telephone2) -> | ||
| 35 | + "".concat(telephone1).concat(",").concat(telephone2)).get(); | ||
| 36 | + SendSmsRequest request = new SendSmsRequest(); | ||
| 37 | + request.setPhoneNumbers(telephones); | ||
| 38 | + request.setSignName(message.getSignature()); | ||
| 39 | + request.setTemplateCode(message.getTemplateId()); | ||
| 40 | + if (Objects.nonNull(message.getParams())) { | ||
| 41 | + String json = message.getParams().entrySet().stream() | ||
| 42 | + .filter(p -> Objects.nonNull(p.getKey()) && Objects.nonNull(p.getValue())) | ||
| 43 | + .map(p -> String.format("\"%s\": \"%s\"", p.getKey(), p.getValue())) | ||
| 44 | + .collect(Collectors.joining(",", "{", "}")); | ||
| 45 | + request.setTemplateParam(json); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + SendSmsResponse response; | ||
| 49 | + try { | ||
| 50 | + response = this.client.sendSms(request); | ||
| 51 | + } catch (Exception ex) { | ||
| 52 | + LOGGER.error("Failed to send ali sms message", ex); | ||
| 53 | + throw new AssistantServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "发送短信失败: 未知错误"); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + SendSmsResponseBody body = response.getBody(); | ||
| 57 | + if ("OK".equalsIgnoreCase(body.getCode())) { | ||
| 58 | + return body.getBizId(); | ||
| 59 | + } else { | ||
| 60 | + LOGGER.error("Failed to send ali sms message, {}:{}", body.getCode(), body.getMessage()); | ||
| 61 | + throw new AssistantServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "发送短信失败: " + body.getMessage()); | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/client/SmsChineseHttpClient.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/client/SmsChineseHttpClient.java | ||
| 1 | +package com.diligrp.cashier.assistant.client; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 4 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 5 | +import com.diligrp.cashier.shared.service.ServiceEndpointSupport; | ||
| 6 | +import org.slf4j.Logger; | ||
| 7 | +import org.slf4j.LoggerFactory; | ||
| 8 | + | ||
| 9 | +public class SmsChineseHttpClient extends ServiceEndpointSupport { | ||
| 10 | + | ||
| 11 | + private static final Logger LOGGER = LoggerFactory.getLogger(SmsChineseHttpClient.class); | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * 接口地址 | ||
| 15 | + */ | ||
| 16 | + private final String uri; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 用户名 | ||
| 20 | + */ | ||
| 21 | + private final String uid; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 短信密钥 | ||
| 25 | + */ | ||
| 26 | + private final String secretKey; | ||
| 27 | + | ||
| 28 | + public SmsChineseHttpClient(String uri, String uid, String secretKey) { | ||
| 29 | + this.uri = uri; | ||
| 30 | + this.uid = uid; | ||
| 31 | + this.secretKey = secretKey; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + public String sendSmsMessage(String telephone, String message, String signature) { | ||
| 35 | + HttpParam[] params = new HttpParam[4]; | ||
| 36 | + params[0] = HttpParam.create("Uid", uid); | ||
| 37 | + params[1] = HttpParam.create("Key", secretKey); | ||
| 38 | + params[2] = HttpParam.create("smsMob", telephone); | ||
| 39 | + params[3] = HttpParam.create("smsText", String.format("【%s】%s", signature, message)); | ||
| 40 | + HttpResult result = send(uri, params); | ||
| 41 | + if (result.statusCode != 200) { | ||
| 42 | + throw new AssistantServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "调用网建短信服务失败"); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + int code = Integer.parseInt(result.responseText); | ||
| 46 | + if (code <= 0) { | ||
| 47 | + LOGGER.error("发送短信失败, 错误码: {}", code); | ||
| 48 | + throw new AssistantServiceException(ErrorCode.SERVICE_ACCESS_ERROR, "发送短信失败, " + errorMessage(code)); | ||
| 49 | + } | ||
| 50 | + return null; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + private String errorMessage(int code) { | ||
| 54 | + return switch (code) { | ||
| 55 | + case -1, -11 -> "账号不存在或被禁用"; | ||
| 56 | + case -2, -21 -> "接口密钥错误"; | ||
| 57 | + case -3 -> "短信数量不足"; | ||
| 58 | + case -4, -41 -> "手机号码为空或格式不正确"; | ||
| 59 | + case -14, -42 -> "短信内容为空或出现非法字符"; | ||
| 60 | + case -51, -52 -> "短信签名太长或格式不正确"; | ||
| 61 | + default -> "未知错误"; | ||
| 62 | + }; | ||
| 63 | + } | ||
| 64 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SequenceKeyDao.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SequenceKeyDao.java | ||
| 1 | +package com.diligrp.cashier.assistant.dao; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.model.PersistentSequenceKey; | ||
| 4 | +import com.diligrp.cashier.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SmsMessageDao.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SmsMessageDao.java | ||
| 1 | +package com.diligrp.cashier.assistant.dao; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.model.SmsMessageDO; | ||
| 4 | +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; | ||
| 5 | +import org.springframework.stereotype.Repository; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 8 | +import java.util.Optional; | ||
| 9 | + | ||
| 10 | +@Repository("smsMessageDao") | ||
| 11 | +public interface SmsMessageDao extends MybatisMapperSupport { | ||
| 12 | + void insertSmsMessage(SmsMessageDO smsMessage); | ||
| 13 | + | ||
| 14 | + void insertSmsMessages(List<SmsMessageDO> smsMessages); | ||
| 15 | + | ||
| 16 | + Optional<SmsMessageDO> findSmsMessageById(String messageId); | ||
| 17 | + | ||
| 18 | + int updateSmsMessageState(SmsMessageDO smsMessage); | ||
| 19 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SmsTemplateDao.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/dao/SmsTemplateDao.java | ||
| 1 | +package com.diligrp.cashier.assistant.dao; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.TemplateStateDTO; | ||
| 4 | +import com.diligrp.cashier.assistant.model.SmsTemplateDO; | ||
| 5 | +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; | ||
| 6 | +import org.springframework.stereotype.Repository; | ||
| 7 | + | ||
| 8 | +import java.util.Optional; | ||
| 9 | + | ||
| 10 | +@Repository("smsTemplateDao") | ||
| 11 | +public interface SmsTemplateDao extends MybatisMapperSupport { | ||
| 12 | + void insertSmsTemplate(SmsTemplateDO template); | ||
| 13 | + | ||
| 14 | + Optional<SmsTemplateDO> findSmsTemplateById(String templateId); | ||
| 15 | + | ||
| 16 | + int updateSmsTemplate(SmsTemplateDO template); | ||
| 17 | + | ||
| 18 | + int deleteSmsTemplate(String templateId); | ||
| 19 | + | ||
| 20 | + int compareAndSetState(TemplateStateDTO stateDTO); | ||
| 21 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SequenceKey.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SequenceKey.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +import java.time.LocalDate; | ||
| 4 | + | ||
| 5 | +public class SequenceKey { | ||
| 6 | + private final long sequence; | ||
| 7 | + private final 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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SequenceKeyDTO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SequenceKeyDTO.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +import java.time.LocalDate; | ||
| 4 | + | ||
| 5 | +public class SequenceKeyDTO { | ||
| 6 | + /** | ||
| 7 | + * KEY标识 | ||
| 8 | + */ | ||
| 9 | + private String key; | ||
| 10 | + /** | ||
| 11 | + * Key名称 | ||
| 12 | + */ | ||
| 13 | + private String name; | ||
| 14 | + /** | ||
| 15 | + * 起始值 | ||
| 16 | + */ | ||
| 17 | + private Long value; | ||
| 18 | + /** | ||
| 19 | + * 步长 | ||
| 20 | + */ | ||
| 21 | + private Integer step; | ||
| 22 | + /** | ||
| 23 | + * ID格式 | ||
| 24 | + */ | ||
| 25 | + private String pattern; | ||
| 26 | + /** | ||
| 27 | + * 有效日期 | ||
| 28 | + */ | ||
| 29 | + private LocalDate expiredOn; | ||
| 30 | + | ||
| 31 | + public String getKey() { | ||
| 32 | + return key; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public void setKey(String key) { | ||
| 36 | + this.key = key; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public String getName() { | ||
| 40 | + return name; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public void setName(String name) { | ||
| 44 | + this.name = name; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + public Long getValue() { | ||
| 48 | + return value; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + public void setValue(Long value) { | ||
| 52 | + this.value = value; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + public Integer getStep() { | ||
| 56 | + return step; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + public void setStep(Integer step) { | ||
| 60 | + this.step = step; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + public String getPattern() { | ||
| 64 | + return pattern; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public void setPattern(String pattern) { | ||
| 68 | + this.pattern = pattern; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + public LocalDate getExpiredOn() { | ||
| 72 | + return expiredOn; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + public void setExpiredOn(LocalDate expiredOn) { | ||
| 76 | + this.expiredOn = expiredOn; | ||
| 77 | + } | ||
| 78 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsMessage.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsMessage.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +import org.apache.commons.text.StringSubstitutor; | ||
| 4 | + | ||
| 5 | +import java.util.HashMap; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Map; | ||
| 8 | +import java.util.Objects; | ||
| 9 | + | ||
| 10 | +public class SmsMessage { | ||
| 11 | + // 短信服务通道模版标识 - 支持短信模版的服务通道使用 | ||
| 12 | + private final String templateId; | ||
| 13 | + // 手机号 | ||
| 14 | + private final List<String> telephones; | ||
| 15 | + // 解析模版变量前的原始内容 | ||
| 16 | + private final String rawContent; | ||
| 17 | + // 解析模版变量后的短信内容 | ||
| 18 | + private String content; | ||
| 19 | + // 模版变量 | ||
| 20 | + private final Map<String, String> params; | ||
| 21 | + // 消息签名 | ||
| 22 | + private final String signature; | ||
| 23 | + | ||
| 24 | + public SmsMessage(String templateId, List<String> telephones, String rawContent, Map<String, String> params, String signature) { | ||
| 25 | + this.templateId = templateId; | ||
| 26 | + this.telephones = telephones; | ||
| 27 | + this.rawContent = rawContent; | ||
| 28 | + this.params = Objects.nonNull(params) ? new HashMap<>(params) : null; | ||
| 29 | + this.signature = signature; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public String getTemplateId() { | ||
| 33 | + return templateId; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public List<String> getTelephones() { | ||
| 37 | + return telephones; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public synchronized String getContent() { // 延迟解析模版变量 | ||
| 41 | + if (content == null) { | ||
| 42 | + content = parseContent(); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + return content; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public String getRawContent() { | ||
| 49 | + return rawContent; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public Map<String, String> getParams() { | ||
| 53 | + return params; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public String getSignature() { | ||
| 57 | + return signature; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + private String parseContent() { | ||
| 61 | + if (Objects.nonNull(this.params)) { // 解析模版变量 | ||
| 62 | + StringSubstitutor engine = new StringSubstitutor(this.params); | ||
| 63 | + return engine.replace(this.rawContent); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + return rawContent; | ||
| 67 | + } | ||
| 68 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsMessageDTO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsMessageDTO.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +import java.util.List; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +public class SmsMessageDTO { | ||
| 7 | + // 短信服务通道模版标识 | ||
| 8 | + private String templateId; | ||
| 9 | + // 手机号 | ||
| 10 | + private String telephone; | ||
| 11 | + // 手机号列表-批量发送时使用 | ||
| 12 | + private List<String> telephones; | ||
| 13 | + // 模版变量 | ||
| 14 | + private Map<String, String> params; | ||
| 15 | + // 消息签名 | ||
| 16 | + private String signature; | ||
| 17 | + // 是否异步发送短信 | ||
| 18 | + private boolean async; | ||
| 19 | + | ||
| 20 | + public String getTemplateId() { | ||
| 21 | + return templateId; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public void setTemplateId(String templateId) { | ||
| 25 | + this.templateId = templateId; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public String getTelephone() { | ||
| 29 | + return telephone; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public void setTelephone(String telephone) { | ||
| 33 | + this.telephone = telephone; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public List<String> getTelephones() { | ||
| 37 | + return telephones; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public void setTelephones(List<String> telephones) { | ||
| 41 | + this.telephones = telephones; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public Map<String, String> getParams() { | ||
| 45 | + return params; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public void setParams(Map<String, String> params) { | ||
| 49 | + this.params = params; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public String getSignature() { | ||
| 53 | + return signature; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public void setSignature(String signature) { | ||
| 57 | + this.signature = signature; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + public boolean isAsync() { | ||
| 61 | + return async; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + public void setAsync(boolean async) { | ||
| 65 | + this.async = async; | ||
| 66 | + } | ||
| 67 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsTemplate.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsTemplate.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +public class SmsTemplate { | ||
| 4 | + // 模版ID | ||
| 5 | + private final String id; | ||
| 6 | + // 模版类型 | ||
| 7 | + private final Integer type; | ||
| 8 | + // 模版名称 | ||
| 9 | + private final String name; | ||
| 10 | + // 模版内容 | ||
| 11 | + private final String content; | ||
| 12 | + // 模版描述 | ||
| 13 | + private final String description; | ||
| 14 | + | ||
| 15 | + public SmsTemplate(Integer type, String name, String content, String description) { | ||
| 16 | + this(null, type, name, content, description); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public SmsTemplate(String id, Integer type, String name, String content, String description) { | ||
| 20 | + this.id = id; | ||
| 21 | + this.type = type; | ||
| 22 | + this.name = name; | ||
| 23 | + this.content = content; | ||
| 24 | + this.description = description; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + public String getId() { | ||
| 28 | + return id; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public Integer getType() { | ||
| 32 | + return type; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public String getName() { | ||
| 36 | + return name; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public String getContent() { | ||
| 40 | + return content; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public String getDescription() { | ||
| 44 | + return description; | ||
| 45 | + } | ||
| 46 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsTemplateDTO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/SmsTemplateDTO.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +public class SmsTemplateDTO { | ||
| 4 | + // 模版ID | ||
| 5 | + private String templateId; | ||
| 6 | + // 服务通道 | ||
| 7 | + private Integer pipeline; | ||
| 8 | + // 模版类型 | ||
| 9 | + private Integer type; | ||
| 10 | + // 模版名称 | ||
| 11 | + private String name; | ||
| 12 | + // 模版内容 | ||
| 13 | + private String content; | ||
| 14 | + // 外部短信模版ID - 比如阿里云短信模版ID | ||
| 15 | + private String outTemplateId; | ||
| 16 | + // 备注 | ||
| 17 | + private String description; | ||
| 18 | + | ||
| 19 | + public String getTemplateId() { | ||
| 20 | + return templateId; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public void setTemplateId(String templateId) { | ||
| 24 | + this.templateId = templateId; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + public Integer getPipeline() { | ||
| 28 | + return pipeline; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public void setPipeline(Integer pipeline) { | ||
| 32 | + this.pipeline = pipeline; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public Integer getType() { | ||
| 36 | + return type; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public void setType(Integer type) { | ||
| 40 | + this.type = type; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + public String getName() { | ||
| 44 | + return name; | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + public void setName(String name) { | ||
| 48 | + this.name = name; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + public String getContent() { | ||
| 52 | + return content; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + public void setContent(String content) { | ||
| 56 | + this.content = content; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + public String getOutTemplateId() { | ||
| 60 | + return outTemplateId; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + public void setOutTemplateId(String outTemplateId) { | ||
| 64 | + this.outTemplateId = outTemplateId; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public String getDescription() { | ||
| 68 | + return description; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + public void setDescription(String description) { | ||
| 72 | + this.description = description; | ||
| 73 | + } | ||
| 74 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/TemplateStateDTO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/domain/TemplateStateDTO.java | ||
| 1 | +package com.diligrp.cashier.assistant.domain; | ||
| 2 | + | ||
| 3 | +import java.time.LocalDateTime; | ||
| 4 | + | ||
| 5 | +public class TemplateStateDTO { | ||
| 6 | + // 短信模板ID | ||
| 7 | + private String templateId; | ||
| 8 | + // 模板状态 | ||
| 9 | + private Integer state; | ||
| 10 | + // 数据版本 | ||
| 11 | + private Integer version; | ||
| 12 | + // 修改时间 | ||
| 13 | + private LocalDateTime modifiedTime; | ||
| 14 | + | ||
| 15 | + public static TemplateStateDTO of(String templateId, Integer state, Integer version, LocalDateTime modifiedTime) { | ||
| 16 | + TemplateStateDTO stateDTO = new TemplateStateDTO(); | ||
| 17 | + stateDTO.setTemplateId(templateId); | ||
| 18 | + stateDTO.setState(state); | ||
| 19 | + stateDTO.setVersion(version); | ||
| 20 | + stateDTO.setModifiedTime(modifiedTime); | ||
| 21 | + return stateDTO; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public String getTemplateId() { | ||
| 25 | + return templateId; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public void setTemplateId(String templateId) { | ||
| 29 | + this.templateId = templateId; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public Integer getState() { | ||
| 33 | + return state; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public void setState(Integer state) { | ||
| 37 | + this.state = state; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public Integer getVersion() { | ||
| 41 | + return version; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public void setVersion(Integer version) { | ||
| 45 | + this.version = version; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public LocalDateTime getModifiedTime() { | ||
| 49 | + return modifiedTime; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public void setModifiedTime(LocalDateTime modifiedTime) { | ||
| 53 | + this.modifiedTime = modifiedTime; | ||
| 54 | + } | ||
| 55 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/exception/AssistantServiceException.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/exception/AssistantServiceException.java | ||
| 1 | +package com.diligrp.cashier.assistant.exception; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.exception.PlatformServiceException; | ||
| 4 | + | ||
| 5 | +public class AssistantServiceException extends PlatformServiceException { | ||
| 6 | + public AssistantServiceException(String message) { | ||
| 7 | + super(message); | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + public AssistantServiceException(int code, String message) { | ||
| 11 | + super(code, message); | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + public AssistantServiceException(String message, Throwable ex) { | ||
| 15 | + super(message, ex); | ||
| 16 | + } | ||
| 17 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/PersistentSequenceKey.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/PersistentSequenceKey.java | ||
| 1 | +package com.diligrp.cashier.assistant.model; | ||
| 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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/SmsMessageDO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/SmsMessageDO.java | ||
| 1 | +package com.diligrp.cashier.assistant.model; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.domain.BaseDO; | ||
| 4 | + | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | + | ||
| 7 | +public class SmsMessageDO extends BaseDO { | ||
| 8 | + // 模版ID | ||
| 9 | + private String templateId; | ||
| 10 | + // 服务通道 | ||
| 11 | + private Integer pipeline; | ||
| 12 | + // 短信类型 | ||
| 13 | + private Integer type; | ||
| 14 | + // 消息ID | ||
| 15 | + private String messageId; | ||
| 16 | + // 电话号码 | ||
| 17 | + private String telephone; | ||
| 18 | + // 消息内容 | ||
| 19 | + private String content; | ||
| 20 | + // 消息状态 | ||
| 21 | + private Integer state; | ||
| 22 | + // 外部请求ID | ||
| 23 | + private String outMessageId; | ||
| 24 | + | ||
| 25 | + public String getTemplateId() { | ||
| 26 | + return templateId; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public void setTemplateId(String templateId) { | ||
| 30 | + this.templateId = templateId; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public Integer getPipeline() { | ||
| 34 | + return pipeline; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public void setPipeline(Integer pipeline) { | ||
| 38 | + this.pipeline = pipeline; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public Integer getType() { | ||
| 42 | + return type; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public void setType(Integer type) { | ||
| 46 | + this.type = type; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public String getMessageId() { | ||
| 50 | + return messageId; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public void setMessageId(String messageId) { | ||
| 54 | + this.messageId = messageId; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public String getTelephone() { | ||
| 58 | + return telephone; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + public void setTelephone(String telephone) { | ||
| 62 | + this.telephone = telephone; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public String getContent() { | ||
| 66 | + return content; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public void setContent(String content) { | ||
| 70 | + this.content = content; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public Integer getState() { | ||
| 74 | + return state; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + public void setState(Integer state) { | ||
| 78 | + this.state = state; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + public String getOutMessageId() { | ||
| 82 | + return outMessageId; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + public void setOutMessageId(String outMessageId) { | ||
| 86 | + this.outMessageId = outMessageId; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + public static Builder builder() { | ||
| 90 | + return new SmsMessageDO().new Builder(); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public class Builder { | ||
| 94 | + public Builder templateId(String templateId) { | ||
| 95 | + SmsMessageDO.this.templateId = templateId; | ||
| 96 | + return this; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + public Builder pipeline(int pipeline) { | ||
| 100 | + SmsMessageDO.this.pipeline = pipeline; | ||
| 101 | + return this; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + public Builder type(int type) { | ||
| 105 | + SmsMessageDO.this.type = type; | ||
| 106 | + return this; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + public Builder messageId(String messageId) { | ||
| 110 | + SmsMessageDO.this.messageId = messageId; | ||
| 111 | + return this; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + public Builder telephone(String telephone) { | ||
| 115 | + SmsMessageDO.this.telephone = telephone; | ||
| 116 | + return this; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + public Builder content(String content) { | ||
| 120 | + SmsMessageDO.this.content = content; | ||
| 121 | + return this; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + public Builder state(int state) { | ||
| 125 | + SmsMessageDO.this.state = state; | ||
| 126 | + return this; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + public Builder outMessageId(String outMessageId) { | ||
| 130 | + SmsMessageDO.this.outMessageId = outMessageId; | ||
| 131 | + return this; | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + public Builder createdTime(LocalDateTime createdTime) { | ||
| 135 | + SmsMessageDO.this.createdTime = createdTime; | ||
| 136 | + return this; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + public Builder modifiedTime(LocalDateTime modifiedTime) { | ||
| 140 | + SmsMessageDO.this.modifiedTime = modifiedTime; | ||
| 141 | + return this; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + public SmsMessageDO build() { | ||
| 145 | + return SmsMessageDO.this; | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/SmsTemplateDO.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/model/SmsTemplateDO.java | ||
| 1 | +package com.diligrp.cashier.assistant.model; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.domain.BaseDO; | ||
| 4 | + | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | + | ||
| 7 | +public class SmsTemplateDO extends BaseDO { | ||
| 8 | + // 模版ID | ||
| 9 | + private String templateId; | ||
| 10 | + // 服务通道 | ||
| 11 | + private Integer pipeline; | ||
| 12 | + // 模版类型 | ||
| 13 | + private Integer type; | ||
| 14 | + // 模版名称 | ||
| 15 | + private String name; | ||
| 16 | + // 模版内容 | ||
| 17 | + private String content; | ||
| 18 | + // 模版状态 | ||
| 19 | + private Integer state; | ||
| 20 | + // 备注 | ||
| 21 | + private String description; | ||
| 22 | + // 外部模版ID | ||
| 23 | + private String outTemplateId; | ||
| 24 | + | ||
| 25 | + public String getTemplateId() { | ||
| 26 | + return templateId; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public void setTemplateId(String templateId) { | ||
| 30 | + this.templateId = templateId; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public Integer getPipeline() { | ||
| 34 | + return pipeline; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public void setPipeline(Integer pipeline) { | ||
| 38 | + this.pipeline = pipeline; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public Integer getType() { | ||
| 42 | + return type; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public void setType(Integer type) { | ||
| 46 | + this.type = type; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public String getName() { | ||
| 50 | + return name; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public void setName(String name) { | ||
| 54 | + this.name = name; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public String getContent() { | ||
| 58 | + return content; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + public void setContent(String content) { | ||
| 62 | + this.content = content; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + public Integer getState() { | ||
| 66 | + return state; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public void setState(Integer state) { | ||
| 70 | + this.state = state; | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public String getDescription() { | ||
| 74 | + return description; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + public void setDescription(String description) { | ||
| 78 | + this.description = description; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + public String getOutTemplateId() { | ||
| 82 | + return outTemplateId; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + public void setOutTemplateId(String outTemplateId) { | ||
| 86 | + this.outTemplateId = outTemplateId; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + public static Builder builder() { | ||
| 90 | + return new SmsTemplateDO().new Builder(); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public class Builder { | ||
| 94 | + public Builder templateId(String templateId) { | ||
| 95 | + SmsTemplateDO.this.templateId = templateId; | ||
| 96 | + return this; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + public Builder pipeline(int pipeline) { | ||
| 100 | + SmsTemplateDO.this.pipeline = pipeline; | ||
| 101 | + return this; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + public Builder type(int type) { | ||
| 105 | + SmsTemplateDO.this.type = type; | ||
| 106 | + return this; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + public Builder name(String name) { | ||
| 110 | + SmsTemplateDO.this.name = name; | ||
| 111 | + return this; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + public Builder content(String content) { | ||
| 115 | + SmsTemplateDO.this.content = content; | ||
| 116 | + return this; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + public Builder state(int state) { | ||
| 120 | + SmsTemplateDO.this.state = state; | ||
| 121 | + return this; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + public Builder description(String description) { | ||
| 125 | + SmsTemplateDO.this.description = description; | ||
| 126 | + return this; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + public Builder outTemplateId(String outTemplateId) { | ||
| 130 | + SmsTemplateDO.this.outTemplateId = outTemplateId; | ||
| 131 | + return this; | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + public Builder version(Integer version) { | ||
| 135 | + SmsTemplateDO.this.version = version; | ||
| 136 | + return this; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + public Builder createdTime(LocalDateTime createdTime) { | ||
| 140 | + SmsTemplateDO.this.createdTime = createdTime; | ||
| 141 | + return this; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + public Builder modifiedTime(LocalDateTime modifiedTime) { | ||
| 145 | + SmsTemplateDO.this.modifiedTime = modifiedTime; | ||
| 146 | + return this; | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + public SmsTemplateDO build() { | ||
| 150 | + return SmsTemplateDO.this; | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/Converter.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/Converter.java | ||
| 1 | +package com.diligrp.cashier.assistant.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/DateConverter.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/DateConverter.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 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 AssistantServiceException(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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/KeywordToken.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/KeywordToken.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 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 AssistantServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Unrecognized keyword " + token); | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public String toString() { | ||
| 26 | + return String.format("keyword(%s)", token); | ||
| 27 | + } | ||
| 28 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/LiteralConverter.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/LiteralConverter.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/LiteralToken.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/LiteralToken.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/OptionToken.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/OptionToken.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 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 AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Not supported converter"); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + public String toString() { | ||
| 22 | + return String.format("option(%s)", token); | ||
| 23 | + } | ||
| 24 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/PatternLayout.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/PatternLayout.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/PatternParser.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/PatternParser.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 4 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 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 AssistantServiceException(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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/RandomConverter.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/RandomConverter.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 6 | +import com.diligrp.cashier.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 AssistantServiceException(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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/SequenceConverter.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/SequenceConverter.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 5 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 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 AssistantServiceException(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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/Token.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/Token.java | ||
| 1 | +package com.diligrp.cashier.assistant.pattern; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/AliSmsPipeline.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/AliSmsPipeline.java | ||
| 1 | +package com.diligrp.cashier.assistant.pipeline; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.client.AliSmsHttpClient; | ||
| 4 | +import com.diligrp.cashier.assistant.domain.SmsMessage; | ||
| 5 | +import com.diligrp.cashier.assistant.type.SmsPipelineType; | ||
| 6 | + | ||
| 7 | +public class AliSmsPipeline extends SmsPipeline { | ||
| 8 | + private AliSmsHttpClient client; | ||
| 9 | + | ||
| 10 | + public AliSmsPipeline(int code, String name, String endPoint, String accessKeyId, String accessKeySecret) { | ||
| 11 | + super(code, name, SmsPipelineType.SMS_ALI); | ||
| 12 | + this.client = new AliSmsHttpClient(endPoint, accessKeyId, accessKeySecret); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + @Override | ||
| 16 | + public String sendSmsMessage(SmsMessage message) { | ||
| 17 | + return this.client.sendSmsMessage(message); | ||
| 18 | + } | ||
| 19 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/DefaultSmsPipelineManager.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/DefaultSmsPipelineManager.java | ||
| 1 | +package com.diligrp.cashier.assistant.pipeline; | ||
| 2 | + | ||
| 3 | +import org.springframework.beans.factory.DisposableBean; | ||
| 4 | + | ||
| 5 | +import java.util.ArrayList; | ||
| 6 | +import java.util.List; | ||
| 7 | + | ||
| 8 | +public class DefaultSmsPipelineManager implements SmsPipelineManager, DisposableBean { | ||
| 9 | + private List<SmsPipeline> pipelines; | ||
| 10 | + | ||
| 11 | + public DefaultSmsPipelineManager() { | ||
| 12 | + this.pipelines = new ArrayList<>(); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + @Override | ||
| 16 | + public void registerPipeline(SmsPipeline pipeline) { | ||
| 17 | + this.pipelines.add(pipeline); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + @Override | ||
| 21 | + public List<SmsPipeline> pipelines() { | ||
| 22 | + return this.pipelines; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + public void destroy() { | ||
| 27 | + for (SmsPipeline pipeline : pipelines) { | ||
| 28 | + pipeline.destroy(); | ||
| 29 | + } | ||
| 30 | + } | ||
| 31 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsChinesePipeline.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsChinesePipeline.java | ||
| 1 | +package com.diligrp.cashier.assistant.pipeline; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.client.SmsChineseHttpClient; | ||
| 4 | +import com.diligrp.cashier.assistant.domain.SmsMessage; | ||
| 5 | +import com.diligrp.cashier.assistant.type.SmsPipelineType; | ||
| 6 | + | ||
| 7 | +import java.util.stream.Collectors; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 网建短信服务通道 | ||
| 11 | + */ | ||
| 12 | +public class SmsChinesePipeline extends SmsPipeline { | ||
| 13 | + | ||
| 14 | + private final SmsChineseHttpClient client; | ||
| 15 | + | ||
| 16 | + public SmsChinesePipeline(int code, String name, String uri, String uid, String secretKey) { | ||
| 17 | + super(code, name, SmsPipelineType.SMS_CHINESE); | ||
| 18 | + this.client = new SmsChineseHttpClient(uri, uid, secretKey); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + @Override | ||
| 22 | + public String sendSmsMessage(SmsMessage message) { | ||
| 23 | + String telephones = message.getTelephones().stream().collect(Collectors.joining()); | ||
| 24 | + return client.sendSmsMessage(telephones, message.getContent(), message.getSignature()); | ||
| 25 | + } | ||
| 26 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsPipeline.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsPipeline.java | ||
| 1 | +package com.diligrp.cashier.assistant.pipeline; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SmsMessage; | ||
| 4 | +import com.diligrp.cashier.assistant.type.SmsPipelineType; | ||
| 5 | + | ||
| 6 | +public abstract class SmsPipeline { | ||
| 7 | + // 通道编码 | ||
| 8 | + protected final int code; | ||
| 9 | + // 通道名称 | ||
| 10 | + protected final String name; | ||
| 11 | + // 通道类型 | ||
| 12 | + protected final SmsPipelineType type; | ||
| 13 | + | ||
| 14 | + public SmsPipeline(int code, String name, SmsPipelineType type) { | ||
| 15 | + this.code = code; | ||
| 16 | + this.name = name; | ||
| 17 | + this.type = type; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 根据短信模版发送短信 | ||
| 22 | + * | ||
| 23 | + * @param message - 短信 | ||
| 24 | + * @return 短信唯一标识 | ||
| 25 | + */ | ||
| 26 | + public abstract String sendSmsMessage(SmsMessage message); | ||
| 27 | + | ||
| 28 | + public void destroy() { | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 获取通道code | ||
| 33 | + */ | ||
| 34 | + public int getCode() { | ||
| 35 | + return this.code; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 获取通道编码 | ||
| 40 | + */ | ||
| 41 | + public String getName() { | ||
| 42 | + return this.name; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 获取通道类型 | ||
| 47 | + */ | ||
| 48 | + public SmsPipelineType getType() { | ||
| 49 | + return this.type; | ||
| 50 | + } | ||
| 51 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsPipelineManager.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pipeline/SmsPipelineManager.java | ||
| 1 | +package com.diligrp.cashier.assistant.pipeline; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 4 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 5 | + | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Optional; | ||
| 8 | + | ||
| 9 | +public interface SmsPipelineManager { | ||
| 10 | + | ||
| 11 | + void registerPipeline(SmsPipeline pipeline); | ||
| 12 | + | ||
| 13 | + List<SmsPipeline> pipelines(); | ||
| 14 | + | ||
| 15 | + default SmsPipeline findPipelineByCode(int code) { | ||
| 16 | + Optional<SmsPipeline> pipeline = pipelines().stream().filter(p -> p.code == code).findAny(); | ||
| 17 | + return pipeline.orElseThrow(() -> new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统未配置此短信服务通道")); | ||
| 18 | + } | ||
| 19 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/KeyGenerator.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/KeyGenerator.java | ||
| 1 | +package com.diligrp.cashier.assistant.service; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * SequenceKey基础类 | ||
| 5 | + */ | ||
| 6 | +public interface KeyGenerator { | ||
| 7 | + /** | ||
| 8 | + * 获取下一个ID | ||
| 9 | + * | ||
| 10 | + * @return 下一个ID | ||
| 11 | + */ | ||
| 12 | + String nextId(); | ||
| 13 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SequenceKeyService.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SequenceKeyService.java | ||
| 1 | +package com.diligrp.cashier.assistant.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.model.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 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SmsMessageService.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SmsMessageService.java | ||
| 1 | +package com.diligrp.cashier.assistant.service; | ||
| 2 | + | ||
| 3 | +import java.util.List; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +public interface SmsMessageService { | ||
| 7 | + /** | ||
| 8 | + * 发送短信 | ||
| 9 | + * | ||
| 10 | + * @param templateId - 短信模版ID | ||
| 11 | + * @param telephones - 手机号列表 | ||
| 12 | + * @param params - 参数 | ||
| 13 | + * @param signature - 网建短信签名 | ||
| 14 | + */ | ||
| 15 | + void sendSmsMessage(String templateId, List<String> telephones, Map<String, String> params, String signature); | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 进行必要性检查后异步发送短信 | ||
| 19 | + * | ||
| 20 | + * @param templateId - 短信模版ID | ||
| 21 | + * @param telephones - 手机号列表 | ||
| 22 | + * @param params - 参数 | ||
| 23 | + * @param signature - 网建短信签名 | ||
| 24 | + */ | ||
| 25 | + void asyncSendSmsMessage(String templateId, List<String> telephones, Map<String, String> params, String signature); | ||
| 26 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SmsTemplateService.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/SmsTemplateService.java | ||
| 1 | +package com.diligrp.cashier.assistant.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.domain.SmsTemplateDTO; | ||
| 4 | + | ||
| 5 | +public interface SmsTemplateService { | ||
| 6 | + /** | ||
| 7 | + * 创建短信模版 | ||
| 8 | + * | ||
| 9 | + * @param smsTemplate - 短信模版 | ||
| 10 | + * @return 模版ID | ||
| 11 | + */ | ||
| 12 | + String createSmsTemplate(SmsTemplateDTO smsTemplate); | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * 修改短信模版 | ||
| 16 | + * | ||
| 17 | + * @param smsTemplate - 短信模版 | ||
| 18 | + */ | ||
| 19 | + void modifySmsTemplate(SmsTemplateDTO smsTemplate); | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 短信模版审批通过 | ||
| 23 | + * | ||
| 24 | + * @param templateId - 短信模版ID | ||
| 25 | + */ | ||
| 26 | + void approveSmsTemplate(String templateId); | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 禁用短信模版 | ||
| 30 | + * | ||
| 31 | + * @param templateId - 短信模版ID | ||
| 32 | + */ | ||
| 33 | + void disableSmsTemplate(String templateId); | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 启用短信模版 | ||
| 37 | + * | ||
| 38 | + * @param templateId - 短信模版ID | ||
| 39 | + */ | ||
| 40 | + void enableSmsTemplate(String templateId); | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 删除短信模版 | ||
| 44 | + * | ||
| 45 | + * @param templateId - 短信模版ID | ||
| 46 | + */ | ||
| 47 | + void deleteSmsTemplate(String templateId); | ||
| 48 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/KeyGeneratorManager.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/KeyGeneratorManager.java | ||
| 1 | +package com.diligrp.cashier.assistant.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.model.PersistentSequenceKey; | ||
| 4 | +import com.diligrp.cashier.assistant.domain.SequenceKey; | ||
| 5 | +import com.diligrp.cashier.assistant.pattern.PatternLayout; | ||
| 6 | +import com.diligrp.cashier.assistant.service.KeyGenerator; | ||
| 7 | +import com.diligrp.cashier.assistant.service.SequenceKeyService; | ||
| 8 | +import com.diligrp.cashier.shared.util.AssertUtils; | ||
| 9 | +import org.springframework.stereotype.Service; | ||
| 10 | + | ||
| 11 | +import java.time.LocalDate; | ||
| 12 | +import java.util.concurrent.ConcurrentHashMap; | ||
| 13 | +import java.util.concurrent.ConcurrentMap; | ||
| 14 | +import java.util.concurrent.TimeUnit; | ||
| 15 | +import java.util.concurrent.locks.Lock; | ||
| 16 | +import java.util.concurrent.locks.ReentrantLock; | ||
| 17 | + | ||
| 18 | +@Service("keyGeneratorManager") | ||
| 19 | +public class KeyGeneratorManager { | ||
| 20 | + private final SequenceKeyService sequenceKeyService; | ||
| 21 | + | ||
| 22 | + private final Lock locker = new ReentrantLock(); | ||
| 23 | + | ||
| 24 | + private final ConcurrentMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>(); | ||
| 25 | + | ||
| 26 | + public KeyGeneratorManager(SequenceKeyService sequenceKeyService) { | ||
| 27 | + this.sequenceKeyService = sequenceKeyService; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public KeyGenerator getKeyGenerator(String key) { | ||
| 31 | + AssertUtils.notNull(key, "Miss key parameter"); | ||
| 32 | + | ||
| 33 | + KeyGenerator keyGenerator = keyGenerators.get(key); | ||
| 34 | + // First check, no need synchronize code block | ||
| 35 | + if (keyGenerator == null) { | ||
| 36 | + boolean result = false; | ||
| 37 | + try { | ||
| 38 | + result = locker.tryLock(15, TimeUnit.SECONDS); | ||
| 39 | + if (!result) { | ||
| 40 | + throw new RuntimeException("Timeout to get KeyGenerator for " + key); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // Double check for performance purpose | ||
| 44 | + if ((keyGenerator = keyGenerators.get(key)) == null) { | ||
| 45 | + PersistentSequenceKey persistentKey = sequenceKeyService.findSequenceKey(key); | ||
| 46 | + if (persistentKey.getExpiredOn() == null) { | ||
| 47 | + keyGenerator = new KeyGeneratorImpl(persistentKey.getId(), key, persistentKey.getPattern()); | ||
| 48 | + } else { | ||
| 49 | + keyGenerator = new ExpiredKeyGeneratorImpl(persistentKey.getId(), key, persistentKey.getPattern()); | ||
| 50 | + } | ||
| 51 | + keyGenerators.put(key, keyGenerator); | ||
| 52 | + } | ||
| 53 | + } catch (InterruptedException iex) { | ||
| 54 | + throw new RuntimeException("Interrupted to get KeyGenerator for " + key, iex); | ||
| 55 | + } finally { | ||
| 56 | + if (result) { | ||
| 57 | + locker.unlock(); | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + return keyGenerator; | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + private class KeyGeneratorImpl implements KeyGenerator { | ||
| 66 | + private final long id; | ||
| 67 | + private final String key; | ||
| 68 | + private final PatternLayout layout; | ||
| 69 | + private long startWith; | ||
| 70 | + private long endWith; | ||
| 71 | + private final Lock keyLocker = new ReentrantLock(); | ||
| 72 | + | ||
| 73 | + public KeyGeneratorImpl(long id, String key, String pattern) { | ||
| 74 | + this.id = id; | ||
| 75 | + this.key = key; | ||
| 76 | + this.startWith = 0; | ||
| 77 | + this.endWith = -1; | ||
| 78 | + if (pattern != null) { | ||
| 79 | + this.layout = new PatternLayout(pattern); | ||
| 80 | + } else { | ||
| 81 | + this.layout = null; | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public String nextId() { | ||
| 87 | + boolean result = false; | ||
| 88 | + try { | ||
| 89 | + result = keyLocker.tryLock(15L, TimeUnit.SECONDS); | ||
| 90 | + if (!result) { | ||
| 91 | + throw new RuntimeException("Timeout to get KeyGenerator for " + key); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + if (this.startWith <= this.endWith) { | ||
| 95 | + if (this.layout != null) { | ||
| 96 | + SequenceKey context = new SequenceKey(this.startWith++, LocalDate.now()); | ||
| 97 | + return layout.doLayout(context); | ||
| 98 | + } else { | ||
| 99 | + return String.valueOf(this.startWith++); | ||
| 100 | + } | ||
| 101 | + } else { | ||
| 102 | + PersistentSequenceKey sequenceKey = sequenceKeyService.synchronizeSequenceKey(id); | ||
| 103 | + long newValue = sequenceKey.getValue() + sequenceKey.getStep(); | ||
| 104 | + this.startWith = sequenceKey.getValue(); | ||
| 105 | + this.endWith = newValue - 1; | ||
| 106 | + | ||
| 107 | + // Then recursive call for a next ID | ||
| 108 | + return nextId(); | ||
| 109 | + } | ||
| 110 | + } catch (InterruptedException iex) { | ||
| 111 | + throw new RuntimeException("Interrupted to get KeyGenerator for " + key, iex); | ||
| 112 | + } finally { | ||
| 113 | + if (result) { | ||
| 114 | + keyLocker.unlock(); | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + private class ExpiredKeyGeneratorImpl implements KeyGenerator { | ||
| 121 | + private final long id; | ||
| 122 | + private final String key; | ||
| 123 | + private final PatternLayout layout; | ||
| 124 | + | ||
| 125 | + private ExpiredKeyGeneratorImpl(long id, String key, String pattern) { | ||
| 126 | + this.id = id; | ||
| 127 | + this.key = key; | ||
| 128 | + if (pattern != null) { | ||
| 129 | + this.layout = new PatternLayout(pattern); | ||
| 130 | + } else { | ||
| 131 | + this.layout = null; | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + @Override | ||
| 136 | + public String nextId() { | ||
| 137 | + //悲观锁添加行锁 - 多JVM多线程场景下自动实现线程同步 | ||
| 138 | + PersistentSequenceKey persistentKey = sequenceKeyService.synchronizeSequenceKey(id); | ||
| 139 | + if (persistentKey == null) { | ||
| 140 | + throw new RuntimeException("Unregistered service key generator: " + key); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + if (this.layout != null) { | ||
| 144 | + SequenceKey context = new SequenceKey(persistentKey.getValue(), persistentKey.getToday()); | ||
| 145 | + return layout.doLayout(context); | ||
| 146 | + } else { | ||
| 147 | + return String.valueOf(persistentKey.getValue()); | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SequenceKeyServiceImpl.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SequenceKeyServiceImpl.java | ||
| 1 | +package com.diligrp.cashier.assistant.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.dao.SequenceKeyDao; | ||
| 4 | +import com.diligrp.cashier.assistant.model.PersistentSequenceKey; | ||
| 5 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 6 | +import com.diligrp.cashier.assistant.service.SequenceKeyService; | ||
| 7 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 8 | +import org.springframework.stereotype.Service; | ||
| 9 | +import org.springframework.transaction.annotation.Transactional; | ||
| 10 | + | ||
| 11 | +import java.time.LocalDate; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * SequenceKey数据同步的实现类 | ||
| 15 | + */ | ||
| 16 | +@Service("sequenceKeyService") | ||
| 17 | +public class SequenceKeyServiceImpl implements SequenceKeyService { | ||
| 18 | + | ||
| 19 | + private SequenceKeyDao sequenceKeyDao; | ||
| 20 | + | ||
| 21 | + public SequenceKeyServiceImpl(SequenceKeyDao sequenceKeyDao) { | ||
| 22 | + this.sequenceKeyDao = sequenceKeyDao; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + @Transactional(rollbackFor = Exception.class) | ||
| 27 | + public void registerSequenceKey(PersistentSequenceKey sequenceKey) { | ||
| 28 | + sequenceKeyDao.insertSequenceKey(sequenceKey); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + public PersistentSequenceKey findSequenceKey(String key) { | ||
| 33 | + return sequenceKeyDao.findSequenceKey(key).orElseThrow(() -> | ||
| 34 | + new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Override | ||
| 38 | + public PersistentSequenceKey findSequenceKeyById(Long id) { | ||
| 39 | + return sequenceKeyDao.findSequenceKeyById(id).orElseThrow(() -> | ||
| 40 | + new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 数据库的行锁只有在事务提交之后才会释放,这里使用业务层的Spring事务,因此行锁将不能很快释放,这样势必会降低此代码块的并发性能。 | ||
| 45 | + * 如果新开一个Spring事务Propagation.REQUIRES_NEW,与业务层事务独立将无法保证ID的连续性(不能随着业务层的失败回滚生成的ID) | ||
| 46 | + */ | ||
| 47 | + @Override | ||
| 48 | +// @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) | ||
| 49 | + @Transactional(rollbackFor = Exception.class) | ||
| 50 | + public PersistentSequenceKey synchronizeSequenceKey(Long id) { | ||
| 51 | + // 悲观锁添加行锁 - 多JVM多线程场景下自动实现线程同步 | ||
| 52 | + // 通过SELECT FOR UPDATE锁定了行,当事务提交时将自动释放行锁 | ||
| 53 | + PersistentSequenceKey persistentKey = sequenceKeyDao.lockSequenceKey(id).orElseThrow(() -> | ||
| 54 | + new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "没有配置该SequenceKey")); | ||
| 55 | + | ||
| 56 | + // 当PersistentKey设置了过期时间并且已经过期时, 则value重新设置成1并刷新过期日期为今天,否则设置value+=step | ||
| 57 | + LocalDate today = persistentKey.getToday(); | ||
| 58 | + LocalDate expiredDay = persistentKey.getExpiredOn(); | ||
| 59 | + if (expiredDay != null && today.isAfter(persistentKey.getExpiredOn())) { | ||
| 60 | + persistentKey.setValue(1L); | ||
| 61 | + expiredDay = today; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + PersistentSequenceKey newKey = new PersistentSequenceKey(); | ||
| 65 | + newKey.setId(persistentKey.getId()); | ||
| 66 | + newKey.setValue(persistentKey.getValue() + persistentKey.getStep()); | ||
| 67 | + newKey.setExpiredOn(expiredDay); | ||
| 68 | + | ||
| 69 | + sequenceKeyDao.unlockSequenceKey(newKey); | ||
| 70 | + | ||
| 71 | + return persistentKey; | ||
| 72 | + } | ||
| 73 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SmsMessageServiceImpl.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SmsMessageServiceImpl.java | ||
| 1 | +package com.diligrp.cashier.assistant.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.dao.SmsMessageDao; | ||
| 4 | +import com.diligrp.cashier.assistant.dao.SmsTemplateDao; | ||
| 5 | +import com.diligrp.cashier.assistant.domain.SmsMessage; | ||
| 6 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 7 | +import com.diligrp.cashier.assistant.model.SmsMessageDO; | ||
| 8 | +import com.diligrp.cashier.assistant.model.SmsTemplateDO; | ||
| 9 | +import com.diligrp.cashier.assistant.pipeline.SmsPipeline; | ||
| 10 | +import com.diligrp.cashier.assistant.pipeline.SmsPipelineManager; | ||
| 11 | +import com.diligrp.cashier.assistant.service.SmsMessageService; | ||
| 12 | +import com.diligrp.cashier.assistant.type.SmsState; | ||
| 13 | +import com.diligrp.cashier.assistant.type.TemplateState; | ||
| 14 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 15 | +import com.diligrp.cashier.shared.service.ThreadPoolService; | ||
| 16 | +import com.diligrp.cashier.shared.util.RandomUtils; | ||
| 17 | +import jakarta.annotation.Resource; | ||
| 18 | +import org.springframework.stereotype.Service; | ||
| 19 | +import org.springframework.transaction.annotation.Transactional; | ||
| 20 | + | ||
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.util.List; | ||
| 23 | +import java.util.Map; | ||
| 24 | +import java.util.stream.Collectors; | ||
| 25 | + | ||
| 26 | +@Service("smsMessageService") | ||
| 27 | +public class SmsMessageServiceImpl implements SmsMessageService { | ||
| 28 | + | ||
| 29 | + @Resource | ||
| 30 | + private SmsTemplateDao smsTemplateDao; | ||
| 31 | + | ||
| 32 | + @Resource | ||
| 33 | + private SmsMessageDao smsMessageDao; | ||
| 34 | + | ||
| 35 | + @Resource | ||
| 36 | + private SmsPipelineManager smsPipelineManager; | ||
| 37 | + | ||
| 38 | + @Override | ||
| 39 | + @Transactional(rollbackFor = Exception.class) | ||
| 40 | + public void sendSmsMessage(String templateId, List<String> telephones, Map<String, String> params, String signature) { | ||
| 41 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 42 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 43 | + if (TemplateState.PENDING.equalsTo(smsTemplate.getState()) || TemplateState.FAILED.equalsTo(smsTemplate.getState())) { | ||
| 44 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版未审核通过,不能发送短信"); | ||
| 45 | + } | ||
| 46 | + if (TemplateState.DISABLED.equalsTo(smsTemplate.getState())) { | ||
| 47 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版被禁用,不能发送短信"); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + LocalDateTime now = LocalDateTime.now(); | ||
| 51 | + SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline()); | ||
| 52 | + SmsMessage smsMessage = new SmsMessage(smsTemplate.getOutTemplateId(), telephones, smsTemplate.getContent(), params, signature); | ||
| 53 | + | ||
| 54 | + String outMessageId = pipeline.sendSmsMessage(smsMessage); | ||
| 55 | + List<SmsMessageDO> smsMessages = telephones.stream().map(telephone -> SmsMessageDO.builder() | ||
| 56 | + .templateId(templateId).pipeline(smsTemplate.getPipeline()).type(smsTemplate.getType()) | ||
| 57 | + .messageId(RandomUtils.randomUUID()).telephone(telephone).content(smsMessage.getContent()) | ||
| 58 | + .state(SmsState.SUCCESS.getCode()).outMessageId(outMessageId).createdTime(now).modifiedTime(now).build()) | ||
| 59 | + .collect(Collectors.toList()); | ||
| 60 | + smsMessageDao.insertSmsMessages(smsMessages); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Override | ||
| 64 | + public void asyncSendSmsMessage(String templateId, List<String> telephones, Map<String, String> params, String signature) { | ||
| 65 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 66 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 67 | + if (TemplateState.PENDING.equalsTo(smsTemplate.getState()) || TemplateState.FAILED.equalsTo(smsTemplate.getState())) { | ||
| 68 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版未审核通过,不能发送短信"); | ||
| 69 | + } | ||
| 70 | + if (TemplateState.DISABLED.equalsTo(smsTemplate.getState())) { | ||
| 71 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版被禁用,不能发送短信"); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + LocalDateTime now = LocalDateTime.now(); | ||
| 75 | + SmsPipeline pipeline = smsPipelineManager.findPipelineByCode(smsTemplate.getPipeline()); | ||
| 76 | + | ||
| 77 | + ThreadPoolService.getIoThreadPoll().submit(() -> { | ||
| 78 | + SmsMessage smsMessage = new SmsMessage(smsTemplate.getOutTemplateId(), telephones, smsTemplate.getContent(), params, signature); | ||
| 79 | + String outMessageId = pipeline.sendSmsMessage(smsMessage); | ||
| 80 | + List<SmsMessageDO> smsMessages = telephones.stream().map(telephone -> SmsMessageDO.builder() | ||
| 81 | + .templateId(templateId).pipeline(smsTemplate.getPipeline()).type(smsTemplate.getType()) | ||
| 82 | + .messageId(RandomUtils.randomUUID()).telephone(telephone).content(smsMessage.getContent()) | ||
| 83 | + .state(SmsState.SUCCESS.getCode()).outMessageId(outMessageId).createdTime(now).modifiedTime(now).build()) | ||
| 84 | + .collect(Collectors.toList()); | ||
| 85 | + smsMessageDao.insertSmsMessages(smsMessages); | ||
| 86 | + }); | ||
| 87 | + } | ||
| 88 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SmsTemplateServiceImpl.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SmsTemplateServiceImpl.java | ||
| 1 | +package com.diligrp.cashier.assistant.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.dao.SmsTemplateDao; | ||
| 4 | +import com.diligrp.cashier.assistant.domain.SmsTemplateDTO; | ||
| 5 | +import com.diligrp.cashier.assistant.domain.TemplateStateDTO; | ||
| 6 | +import com.diligrp.cashier.assistant.exception.AssistantServiceException; | ||
| 7 | +import com.diligrp.cashier.assistant.model.SmsTemplateDO; | ||
| 8 | +import com.diligrp.cashier.assistant.service.SmsTemplateService; | ||
| 9 | +import com.diligrp.cashier.assistant.type.SmsType; | ||
| 10 | +import com.diligrp.cashier.assistant.type.TemplateState; | ||
| 11 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 12 | +import jakarta.annotation.Resource; | ||
| 13 | +import org.springframework.stereotype.Service; | ||
| 14 | +import org.springframework.transaction.annotation.Transactional; | ||
| 15 | + | ||
| 16 | +import java.time.LocalDateTime; | ||
| 17 | + | ||
| 18 | +@Service("smsTemplateService") | ||
| 19 | +public class SmsTemplateServiceImpl implements SmsTemplateService { | ||
| 20 | + | ||
| 21 | + @Resource | ||
| 22 | + private SmsTemplateDao smsTemplateDao; | ||
| 23 | + | ||
| 24 | + @Resource | ||
| 25 | + private KeyGeneratorManager keyGeneratorManager; | ||
| 26 | + | ||
| 27 | + @Override | ||
| 28 | + @Transactional(rollbackFor = Exception.class) | ||
| 29 | + public String createSmsTemplate(SmsTemplateDTO smsTemplate) { | ||
| 30 | + LocalDateTime now = LocalDateTime.now(); | ||
| 31 | + SmsType.getType(smsTemplate.getType()).orElseThrow(() -> | ||
| 32 | + new AssistantServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "不能识别的短信模版类型")); | ||
| 33 | + | ||
| 34 | + SmsTemplateDO.Builder template = SmsTemplateDO.builder(); | ||
| 35 | + String templateId = keyGeneratorManager.getKeyGenerator("SMS_TEMPLATE_KEY").nextId(); | ||
| 36 | + template.templateId(templateId).pipeline(smsTemplate.getPipeline()).type(smsTemplate.getType()) | ||
| 37 | + .name(smsTemplate.getName()).content(smsTemplate.getContent()).state(TemplateState.PENDING.getCode()) | ||
| 38 | + .description(smsTemplate.getDescription()).outTemplateId(smsTemplate.getOutTemplateId()).version(0) | ||
| 39 | + .createdTime(now).modifiedTime(now); | ||
| 40 | + smsTemplateDao.insertSmsTemplate(template.build()); | ||
| 41 | + return templateId; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Override | ||
| 45 | + @Transactional(rollbackFor = Exception.class) | ||
| 46 | + public void modifySmsTemplate(SmsTemplateDTO smsTemplate) { | ||
| 47 | + LocalDateTime now = LocalDateTime.now(); | ||
| 48 | + | ||
| 49 | + SmsTemplateDO template = smsTemplateDao.findSmsTemplateById(smsTemplate.getTemplateId()) | ||
| 50 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 51 | + // 与阿里云模版管理逻辑保持一致:只允许修改未审核通过的模版,审核通过的模版只能删除 | ||
| 52 | + if (!TemplateState.PENDING.equalsTo(template.getState()) && !TemplateState.FAILED.equalsTo(template.getState())) { | ||
| 53 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版已审核通过,不允许修改"); | ||
| 54 | + } | ||
| 55 | + if (smsTemplate.getType() != null) { | ||
| 56 | + SmsType.getType(smsTemplate.getType()).orElseThrow(() -> new AssistantServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "不能识别的短信模版类型")); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + SmsTemplateDO smsTemplateDo = SmsTemplateDO.builder().templateId(smsTemplate.getTemplateId()) | ||
| 60 | + .type(smsTemplate.getType()).name(smsTemplate.getName()).content(smsTemplate.getContent()) | ||
| 61 | + .description(smsTemplate.getDescription()).modifiedTime(now).build(); | ||
| 62 | + smsTemplateDao.updateSmsTemplate(smsTemplateDo); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + @Override | ||
| 66 | + @Transactional(rollbackFor = Exception.class) | ||
| 67 | + public void approveSmsTemplate(String templateId) { | ||
| 68 | + LocalDateTime now = LocalDateTime.now(); | ||
| 69 | + | ||
| 70 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 71 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 72 | + if (TemplateState.SUCCESS.equalsTo(smsTemplate.getState())) { | ||
| 73 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版已审批通过"); | ||
| 74 | + } | ||
| 75 | + if (TemplateState.DISABLED.equalsTo(smsTemplate.getState())) { | ||
| 76 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "短信模版已被禁用"); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.SUCCESS.getCode(), | ||
| 80 | + smsTemplate.getVersion(), now); | ||
| 81 | + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) { | ||
| 82 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试"); | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + @Override | ||
| 87 | + public void disableSmsTemplate(String templateId) { | ||
| 88 | + LocalDateTime now = LocalDateTime.now(); | ||
| 89 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 90 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 91 | + if (!TemplateState.SUCCESS.equalsTo(smsTemplate.getState())) { | ||
| 92 | + String error = String.format("不能禁用状态为\"%s\"的短信模版", TemplateState.getName(smsTemplate.getState())); | ||
| 93 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, error); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + // 仅仅允许审核通过的短信模版被禁用 | ||
| 97 | + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.DISABLED.getCode(), | ||
| 98 | + smsTemplate.getVersion(), now); | ||
| 99 | + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) { | ||
| 100 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试"); | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + @Override | ||
| 105 | + @Transactional(rollbackFor = Exception.class) | ||
| 106 | + public void enableSmsTemplate(String templateId) { | ||
| 107 | + LocalDateTime now = LocalDateTime.now(); | ||
| 108 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 109 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 110 | + if (!TemplateState.DISABLED.equalsTo(smsTemplate.getState())) { | ||
| 111 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "仅仅被禁用的短信模版才能被启用"); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + TemplateStateDTO stateDTO = TemplateStateDTO.of(templateId, TemplateState.SUCCESS.getCode(), | ||
| 115 | + smsTemplate.getVersion(), now); | ||
| 116 | + if (smsTemplateDao.compareAndSetState(stateDTO) == 0) { | ||
| 117 | + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "系统忙,请稍后重试"); | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @Override | ||
| 122 | + @Transactional(rollbackFor = Exception.class) | ||
| 123 | + public void deleteSmsTemplate(String templateId) { | ||
| 124 | + SmsTemplateDO smsTemplate = smsTemplateDao.findSmsTemplateById(templateId) | ||
| 125 | + .orElseThrow(() -> new AssistantServiceException(ErrorCode.OBJECT_NOT_FOUND, "短信模版不存在")); | ||
| 126 | + | ||
| 127 | + smsTemplateDao.deleteSmsTemplate(templateId); | ||
| 128 | + } | ||
| 129 | +} |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SnowflakeKeyManager.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/service/impl/SnowflakeKeyManager.java | ||
| 1 | +package com.diligrp.cashier.assistant.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.service.KeyGenerator; | ||
| 4 | +import com.diligrp.cashier.shared.security.HexUtils; | ||
| 5 | +import com.diligrp.cashier.shared.util.AssertUtils; | ||
| 6 | +import com.diligrp.cashier.shared.util.DateUtils; | ||
| 7 | +import org.springframework.stereotype.Service; | ||
| 8 | + | ||
| 9 | +import java.net.InetAddress; | ||
| 10 | +import java.net.NetworkInterface; | ||
| 11 | +import java.nio.charset.StandardCharsets; | ||
| 12 | +import java.security.MessageDigest; | ||
| 13 | +import java.time.LocalDateTime; | ||
| 14 | +import java.time.ZoneOffset; | ||
| 15 | +import java.util.*; | ||
| 16 | +import java.util.concurrent.ConcurrentHashMap; | ||
| 17 | +import java.util.concurrent.ConcurrentMap; | ||
| 18 | +import java.util.concurrent.TimeUnit; | ||
| 19 | +import java.util.concurrent.locks.Lock; | ||
| 20 | +import java.util.concurrent.locks.ReentrantLock; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * 雪花算法KeyManager实现 | ||
| 24 | + */ | ||
| 25 | +@Service("snowflakeKeyManager") | ||
| 26 | +public class SnowflakeKeyManager { | ||
| 27 | + | ||
| 28 | + private final Lock locker = new ReentrantLock(); | ||
| 29 | + | ||
| 30 | + private final ConcurrentMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>(); | ||
| 31 | + | ||
| 32 | + public KeyGenerator getKeyGenerator(SnowflakeKey snowflakeKey) { | ||
| 33 | + AssertUtils.notNull(snowflakeKey, "Miss key parameter"); | ||
| 34 | + | ||
| 35 | + String cachedKey = snowflakeKey.identifier(); | ||
| 36 | + KeyGenerator keyGenerator = keyGenerators.get(cachedKey); | ||
| 37 | + // First check, no need synchronize code block | ||
| 38 | + if (keyGenerator == null) { | ||
| 39 | + boolean result = false; | ||
| 40 | + try { | ||
| 41 | + result = locker.tryLock(15, TimeUnit.SECONDS); | ||
| 42 | + if (!result) { | ||
| 43 | + throw new RuntimeException("Timeout to get SnowflakeKeyGenerator for " + cachedKey); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + // Double check for performance purpose | ||
| 47 | + if ((keyGenerator = keyGenerators.get(cachedKey)) == null) { | ||
| 48 | + keyGenerator = new SnowflakeKeyGenerator(snowflakeKey); | ||
| 49 | + keyGenerators.put(cachedKey, keyGenerator); | ||
| 50 | + } | ||
| 51 | + } catch (InterruptedException iex) { | ||
| 52 | + throw new RuntimeException("Interrupted to get SnowflakeKeyGenerator for " + cachedKey, iex); | ||
| 53 | + } finally { | ||
| 54 | + if (result) { | ||
| 55 | + locker.unlock(); | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + return keyGenerator; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * 雪花算法ID生成器实现 | ||
| 65 | + */ | ||
| 66 | + private static class SnowflakeKeyGenerator implements KeyGenerator { | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * Customer based epoch, unit as second. until 2024-08-08 00:00:00 | ||
| 70 | + */ | ||
| 71 | + private final long epochSeconds = LocalDateTime.of(2024, 8, 8, 0, 0, 0) | ||
| 72 | + .toEpochSecond(ZoneOffset.of("+8")); | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * Stable fields after spring bean initializing | ||
| 76 | + */ | ||
| 77 | + private final BitsAllocator bitsAllocator; | ||
| 78 | + private final long workerId; | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * Volatile fields caused by nextId() | ||
| 82 | + */ | ||
| 83 | + private long sequence = 0L; | ||
| 84 | + private long lastSecond = -1L; | ||
| 85 | + | ||
| 86 | + public SnowflakeKeyGenerator(SnowflakeKey snowflakeKey) { | ||
| 87 | + this.bitsAllocator = new BitsAllocator(snowflakeKey.timeBits(), snowflakeKey.workerBits(), snowflakeKey.seqBits()); | ||
| 88 | + // using pid in case two instances running on the same machine | ||
| 89 | + String serviceFeature = String.format("%s@%s", snowflakeKey.identifier(), ProcessHandle.current().pid()); | ||
| 90 | + this.workerId = new WorkerIdGenerator(snowflakeKey.workerBits()).assignWorkerId(serviceFeature); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + @Override | ||
| 94 | + public synchronized String nextId() { | ||
| 95 | + long currentSecond = getCurrentSecond(); | ||
| 96 | + | ||
| 97 | + // Clock moved backwards, refuse to generate uid | ||
| 98 | + if (currentSecond < lastSecond) { | ||
| 99 | + long refusedSeconds = lastSecond - currentSecond; | ||
| 100 | + throw new RuntimeException(String.format("Clock moved backwards. Refusing for %d seconds", refusedSeconds)); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + // At the same second, increase service | ||
| 104 | + if (currentSecond == lastSecond) { | ||
| 105 | + sequence = (sequence + 1) & bitsAllocator.maxSequence; | ||
| 106 | + // Exceed the max service, we wait the next second to generate uid | ||
| 107 | + if (sequence == 0) { | ||
| 108 | + currentSecond = getNextSecond(lastSecond); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + // At the different second, service restart from zero | ||
| 112 | + } else { | ||
| 113 | + sequence = 0L; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + lastSecond = currentSecond; | ||
| 117 | + | ||
| 118 | + // Allocate bits for UID | ||
| 119 | + return String.valueOf(bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence)); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + public String parseId(long uid) { | ||
| 123 | + long totalBits = BitsAllocator.TOTAL_BITS; | ||
| 124 | + long signBits = bitsAllocator.signBits; | ||
| 125 | + long timestampBits = bitsAllocator.timestampBits; | ||
| 126 | + long workerIdBits = bitsAllocator.workerIdBits; | ||
| 127 | + long sequenceBits = bitsAllocator.sequenceBits; | ||
| 128 | + | ||
| 129 | + // parse UID | ||
| 130 | + long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits); | ||
| 131 | + long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits); | ||
| 132 | + long deltaSeconds = uid >>> (workerIdBits + sequenceBits); | ||
| 133 | + | ||
| 134 | + LocalDateTime when = LocalDateTime.ofEpochSecond(epochSeconds + deltaSeconds, 0, ZoneOffset.of("+8")); | ||
| 135 | + String thatTime = DateUtils.formatDateTime(when); | ||
| 136 | + | ||
| 137 | + // format as string | ||
| 138 | + return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"service\":\"%d\"}", | ||
| 139 | + uid, thatTime, workerId, sequence); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + /** | ||
| 143 | + * Get next millisecond | ||
| 144 | + */ | ||
| 145 | + private long getNextSecond(long lastTimestamp) { | ||
| 146 | + long timestamp = getCurrentSecond(); | ||
| 147 | + while (timestamp <= lastTimestamp) { | ||
| 148 | + timestamp = getCurrentSecond(); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + return timestamp; | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + /** | ||
| 155 | + * Get current second | ||
| 156 | + */ | ||
| 157 | + private long getCurrentSecond() { | ||
| 158 | + long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); | ||
| 159 | + if (currentSecond - epochSeconds > bitsAllocator.maxDeltaSeconds) { | ||
| 160 | + throw new RuntimeException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + return currentSecond; | ||
| 164 | + } | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + /** | ||
| 168 | + * Allocate 64 bits for the UID(long)<br> | ||
| 169 | + * sign (fixed 1bit) -> deltaSecond -> workerId -> service(within the same second) | ||
| 170 | + */ | ||
| 171 | + private static class BitsAllocator { | ||
| 172 | + /** | ||
| 173 | + * Total 64 bits | ||
| 174 | + */ | ||
| 175 | + public static final int TOTAL_BITS = 1 << 6; | ||
| 176 | + | ||
| 177 | + /** | ||
| 178 | + * Bits for [sign-> second-> workId-> service] | ||
| 179 | + */ | ||
| 180 | + private final int signBits = 1; | ||
| 181 | + private final int timestampBits; | ||
| 182 | + private final int workerIdBits; | ||
| 183 | + private final int sequenceBits; | ||
| 184 | + | ||
| 185 | + /** | ||
| 186 | + * Max value for workId & service | ||
| 187 | + */ | ||
| 188 | + private final long maxDeltaSeconds; | ||
| 189 | + private final long maxSequence; | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * Shift for timestamp & workerId | ||
| 193 | + */ | ||
| 194 | + private final int timestampShift; | ||
| 195 | + private final int workerIdShift; | ||
| 196 | + | ||
| 197 | + /** | ||
| 198 | + * Constructor with timestampBits, workerIdBits, sequenceBits<br> | ||
| 199 | + * The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits | ||
| 200 | + */ | ||
| 201 | + public BitsAllocator(int timestampBits, int workerIdBits, int sequenceBits) { | ||
| 202 | + // make sure allocated 64 bits | ||
| 203 | + int allocateTotalBits = signBits + timestampBits + workerIdBits + sequenceBits; | ||
| 204 | + AssertUtils.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits"); | ||
| 205 | + | ||
| 206 | + // initialize bits | ||
| 207 | + this.timestampBits = timestampBits; | ||
| 208 | + this.workerIdBits = workerIdBits; | ||
| 209 | + this.sequenceBits = sequenceBits; | ||
| 210 | + | ||
| 211 | + // initialize max value | ||
| 212 | + this.maxDeltaSeconds = ~(-1L << timestampBits); | ||
| 213 | + this.maxSequence = ~(-1L << sequenceBits); | ||
| 214 | + | ||
| 215 | + // initialize shift | ||
| 216 | + this.timestampShift = workerIdBits + sequenceBits; | ||
| 217 | + this.workerIdShift = sequenceBits; | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + /** | ||
| 221 | + * Allocate bits for UID according to delta seconds & workerId & service<br> | ||
| 222 | + * <b>Note that: </b>The highest bit will always be 0 for sign | ||
| 223 | + */ | ||
| 224 | + public long allocate(long deltaSeconds, long workerId, long sequence) { | ||
| 225 | + return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence; | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + /** | ||
| 230 | + * Assign worker id using host and service feature, avoid worker id conflict | ||
| 231 | + */ | ||
| 232 | + private static class WorkerIdGenerator { | ||
| 233 | + /** | ||
| 234 | + * Host feature | ||
| 235 | + */ | ||
| 236 | + private static final int UNIQUE_HOST_ID = getUniqueHostId(); | ||
| 237 | + | ||
| 238 | + private final int workerIdBits; | ||
| 239 | + | ||
| 240 | + public WorkerIdGenerator(int workerIdBits) { | ||
| 241 | + this.workerIdBits = workerIdBits; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + public long assignWorkerId(String serviceFeature) { | ||
| 245 | + int uniqueServiceId = convertUniqueInt(serviceFeature); | ||
| 246 | + long workerId = ((long) UNIQUE_HOST_ID << 32) | uniqueServiceId; | ||
| 247 | + long maxWorkerId = ~(-1L << workerIdBits); | ||
| 248 | + return workerId & maxWorkerId; | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + private static int getUniqueHostId() { | ||
| 252 | + StringBuilder uniqueFeatures = new StringBuilder(); | ||
| 253 | + try { | ||
| 254 | + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); | ||
| 255 | + List<String> macs = new ArrayList<>(); | ||
| 256 | + while (interfaces.hasMoreElements()) { | ||
| 257 | + NetworkInterface networkInterface = interfaces.nextElement(); | ||
| 258 | + if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) { | ||
| 259 | + continue; | ||
| 260 | + } | ||
| 261 | + byte[] macBytes = networkInterface.getHardwareAddress(); | ||
| 262 | + if (macBytes == null || macBytes.length == 0) { | ||
| 263 | + continue; | ||
| 264 | + } | ||
| 265 | + macs.add(HexUtils.encodeHexStr(macBytes)); | ||
| 266 | + } | ||
| 267 | + | ||
| 268 | + if (!macs.isEmpty()) { | ||
| 269 | + Collections.sort(macs); | ||
| 270 | + String macList = macs.stream().reduce((mac1, mac2) -> mac1 + "," + mac2).orElse("unknown-mac"); | ||
| 271 | + uniqueFeatures.append(macList); | ||
| 272 | + } else { | ||
| 273 | + uniqueFeatures.append(UUID.randomUUID()); | ||
| 274 | + } | ||
| 275 | + } catch (Exception ex) { | ||
| 276 | + throw new RuntimeException("Failed to generate unique host feature", ex); | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + try { | ||
| 280 | + String hostName = InetAddress.getLocalHost().getHostName(); | ||
| 281 | + uniqueFeatures.append("@").append(hostName); | ||
| 282 | + } catch (Exception ex) { | ||
| 283 | + uniqueFeatures.append("@unknown-host"); | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + return convertUniqueInt(uniqueFeatures.toString()); | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + private static int convertUniqueInt(String feature) { | ||
| 290 | + MessageDigest sha; | ||
| 291 | + try { | ||
| 292 | + sha = MessageDigest.getInstance("SHA-256"); | ||
| 293 | + } catch (Exception ex) { | ||
| 294 | + throw new RuntimeException("Failed to init snowflake key", ex); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + sha.update(feature.getBytes(StandardCharsets.UTF_8)); | ||
| 298 | + byte[] bytes = new byte[4]; | ||
| 299 | + System.arraycopy(sha.digest(), 4, bytes, 0, 4); | ||
| 300 | + | ||
| 301 | + // Big endian | ||
| 302 | + int intValue = 0; | ||
| 303 | + intValue |= (bytes[3] & 0xFF); | ||
| 304 | + intValue |= (bytes[2] & 0xFF) << 8; | ||
| 305 | + intValue |= (bytes[1] & 0xFF) << 16; | ||
| 306 | + intValue |= (bytes[0] & 0xFF) << 24; | ||
| 307 | + return intValue; | ||
| 308 | + } | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + public interface SnowflakeKey { | ||
| 312 | + // 默认时间戳位数 | ||
| 313 | + int timeBits = 32; | ||
| 314 | + // 默认机器标识位数 | ||
| 315 | + int workerBits = 19; | ||
| 316 | + // 默认序号位数 | ||
| 317 | + int seqBits = 12; | ||
| 318 | + | ||
| 319 | + default int timeBits() { | ||
| 320 | + return timeBits; | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + default int workerBits() { | ||
| 324 | + return workerBits; | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + default int seqBits() { | ||
| 328 | + return seqBits; | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + default String identifier() { | ||
| 332 | + return this.toString(); | ||
| 333 | + } | ||
| 334 | + } | ||
| 335 | +} | ||
| 0 | \ No newline at end of file | 336 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsPipelineType.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsPipelineType.java | ||
| 1 | +package com.diligrp.cashier.assistant.type; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.type.IEnumType; | ||
| 4 | + | ||
| 5 | +import java.util.Arrays; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Optional; | ||
| 8 | +import java.util.stream.Stream; | ||
| 9 | + | ||
| 10 | +public enum SmsPipelineType implements IEnumType { | ||
| 11 | + SMS_ALI("阿里短信服务", 1), | ||
| 12 | + | ||
| 13 | + SMS_CHINESE("网建短信服务", 2); | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + private final String name; | ||
| 17 | + private final int code; | ||
| 18 | + | ||
| 19 | + SmsPipelineType(String name, int code) { | ||
| 20 | + this.name = name; | ||
| 21 | + this.code = code; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public static Optional<SmsPipelineType> getType(int code) { | ||
| 25 | + Stream<SmsPipelineType> types = Arrays.stream(values()); | ||
| 26 | + return types.filter(type -> type.getCode() == code).findFirst(); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public static String getName(int code) { | ||
| 30 | + Stream<SmsPipelineType> TYPES = Arrays.stream(values()); | ||
| 31 | + Optional<String> result = TYPES.filter(type -> type.getCode() == code) | ||
| 32 | + .map(SmsPipelineType::getName).findFirst(); | ||
| 33 | + return result.orElse(null); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public static List<SmsPipelineType> getTypes() { | ||
| 37 | + return Arrays.asList(values()); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public String getName() { | ||
| 41 | + return this.name; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public int getCode() { | ||
| 45 | + return this.code; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public String toString() { | ||
| 49 | + return this.name; | ||
| 50 | + } | ||
| 51 | +} | ||
| 0 | \ No newline at end of file | 52 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsState.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsState.java | ||
| 1 | +package com.diligrp.cashier.assistant.type; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.type.IEnumType; | ||
| 4 | + | ||
| 5 | +import java.util.Arrays; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Optional; | ||
| 8 | +import java.util.stream.Stream; | ||
| 9 | + | ||
| 10 | +public enum SmsState implements IEnumType { | ||
| 11 | + SUBMITTED("已提交", 1), | ||
| 12 | + | ||
| 13 | + SUCCESS("发送成功", 2), | ||
| 14 | + | ||
| 15 | + FAILED("发送失败", 3); | ||
| 16 | + | ||
| 17 | + private final String name; | ||
| 18 | + private final int code; | ||
| 19 | + | ||
| 20 | + SmsState(String name, int code) { | ||
| 21 | + this.name = name; | ||
| 22 | + this.code = code; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public static Optional<SmsState> getState(int code) { | ||
| 26 | + Stream<SmsState> states = Arrays.stream(values()); | ||
| 27 | + return states.filter(state -> state.getCode() == code).findFirst(); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static String getName(int code) { | ||
| 31 | + Stream<SmsState> states = Arrays.stream(values()); | ||
| 32 | + Optional<String> result = states.filter(state -> state.getCode() == code) | ||
| 33 | + .map(SmsState::getName).findFirst(); | ||
| 34 | + return result.isPresent() ? result.get() : null; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public static List<SmsState> getStates() { | ||
| 38 | + return Arrays.asList(values()); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public String getName() { | ||
| 42 | + return this.name; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public int getCode() { | ||
| 46 | + return this.code; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public String toString() { | ||
| 50 | + return this.name; | ||
| 51 | + } | ||
| 52 | +} | ||
| 0 | \ No newline at end of file | 53 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsType.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/SmsType.java | ||
| 1 | +package com.diligrp.cashier.assistant.type; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.type.IEnumType; | ||
| 4 | + | ||
| 5 | +import java.util.Arrays; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Optional; | ||
| 8 | +import java.util.stream.Stream; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 与阿里云短信服务中的模版类型保持一致,请谨慎修改 | ||
| 12 | + */ | ||
| 13 | +public enum SmsType implements IEnumType { | ||
| 14 | + CAPTCHA("验证码", 0), | ||
| 15 | + | ||
| 16 | + INFORMATION("短信通知", 1), | ||
| 17 | + | ||
| 18 | + PROMOTION("推广短信", 2); | ||
| 19 | + | ||
| 20 | + private final String name; | ||
| 21 | + private final int code; | ||
| 22 | + | ||
| 23 | + SmsType(String name, int code) { | ||
| 24 | + this.name = name; | ||
| 25 | + this.code = code; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public static Optional<SmsType> getType(int code) { | ||
| 29 | + Stream<SmsType> types = Arrays.stream(values()); | ||
| 30 | + return types.filter(type -> type.getCode() == code).findFirst(); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public static String getName(int code) { | ||
| 34 | + Stream<SmsType> TYPES = Arrays.stream(values()); | ||
| 35 | + Optional<String> result = TYPES.filter(type -> type.getCode() == code) | ||
| 36 | + .map(SmsType::getName).findFirst(); | ||
| 37 | + return result.orElse(null); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public static List<SmsType> getTypes() { | ||
| 41 | + return Arrays.asList(values()); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public String getName() { | ||
| 45 | + return this.name; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public int getCode() { | ||
| 49 | + return this.code; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public String toString() { | ||
| 53 | + return this.name; | ||
| 54 | + } | ||
| 55 | +} | ||
| 0 | \ No newline at end of file | 56 | \ No newline at end of file |
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/TemplateState.java
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/java/com/diligrp/cashier/assistant/type/TemplateState.java | ||
| 1 | +package com.diligrp.cashier.assistant.type; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.type.IEnumType; | ||
| 4 | + | ||
| 5 | +import java.util.Arrays; | ||
| 6 | +import java.util.List; | ||
| 7 | +import java.util.Optional; | ||
| 8 | +import java.util.stream.Stream; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 允许的状态变更 | ||
| 12 | + * | ||
| 13 | + * 待审核 -> 审核通过/审核失败 | ||
| 14 | + * 审核通过 -> 禁用 | ||
| 15 | + */ | ||
| 16 | +public enum TemplateState implements IEnumType { | ||
| 17 | + PENDING("待审核", 1), | ||
| 18 | + | ||
| 19 | + SUCCESS("审核通过", 2), | ||
| 20 | + | ||
| 21 | + FAILED("审核失败", 3), | ||
| 22 | + | ||
| 23 | + DISABLED("禁用", 0); | ||
| 24 | + | ||
| 25 | + private final String name; | ||
| 26 | + private final int code; | ||
| 27 | + | ||
| 28 | + TemplateState(String name, int code) { | ||
| 29 | + this.name = name; | ||
| 30 | + this.code = code; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public static Optional<TemplateState> getState(int code) { | ||
| 34 | + Stream<TemplateState> states = Arrays.stream(values()); | ||
| 35 | + return states.filter(state -> state.getCode() == code).findFirst(); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + public static String getName(int code) { | ||
| 39 | + Stream<TemplateState> states = Arrays.stream(values()); | ||
| 40 | + Optional<String> result = states.filter(state -> state.getCode() == code) | ||
| 41 | + .map(TemplateState::getName).findFirst(); | ||
| 42 | + return result.orElse(null); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public static List<TemplateState> getStates() { | ||
| 46 | + return Arrays.asList(values()); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public String getName() { | ||
| 50 | + return this.name; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public int getCode() { | ||
| 54 | + return this.code; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public boolean equalsTo(int code) { | ||
| 58 | + return this.code == code; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + public String toString() { | ||
| 62 | + return this.name; | ||
| 63 | + } | ||
| 64 | +} | ||
| 0 | \ No newline at end of file | 65 | \ No newline at end of file |
cashier-assistant/src/main/resources/com/diligrp/cashier/dao/mapper/SequenceKeyDao.xml
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/resources/com/diligrp/cashier/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.cashier.assistant.dao.SequenceKeyDao"> | ||
| 6 | + <resultMap id="PersistentKeyMap" type="com.diligrp.cashier.assistant.model.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.cashier.assistant.model.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.cashier.assistant.model.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 | \ No newline at end of file | 60 | \ No newline at end of file |
cashier-assistant/src/main/resources/com/diligrp/cashier/dao/mapper/SmsMessageDao.xml
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/resources/com/diligrp/cashier/dao/mapper/SmsMessageDao.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.assistant.sms.dao.SmsMessageDao"> | ||
| 6 | + <resultMap id="SmsMessageMap" type="com.diligrp.cashier.assistant.model.SmsMessageDO"> | ||
| 7 | + <id column="id" property="id"/> | ||
| 8 | + <result column="template_id" property="templateId"/> | ||
| 9 | + <result column="pipeline" property="pipeline"/> | ||
| 10 | + <result column="type" property="type"/> | ||
| 11 | + <result column="message_id" property="messageId"/> | ||
| 12 | + <result column="telephone" property="telephone"/> | ||
| 13 | + <result column="content" property="content"/> | ||
| 14 | + <result column="state" property="state"/> | ||
| 15 | + <result column="out_message_id" property="outMessageId"/> | ||
| 16 | + <result column="created_time" property="createdTime"/> | ||
| 17 | + <result column="modified_time" property="modifiedTime"/> | ||
| 18 | + </resultMap> | ||
| 19 | + | ||
| 20 | + <insert id="insertSmsMessage" parameterType="com.diligrp.cashier.assistant.model.SmsTemplateDO"> | ||
| 21 | + INSERT INTO sms_message | ||
| 22 | + (template_id, pipeline, type, message_id, telephone, content, state, out_message_id, created_time, modified_time) | ||
| 23 | + VALUES | ||
| 24 | + (#{templateId}, #{pipeline}, #{type}, #{messageId}, #{telephone}, #{content}, #{state}, #{outMessageId}, #{createdTime}, #{modifiedTime}) | ||
| 25 | + </insert> | ||
| 26 | + | ||
| 27 | + <insert id="insertSmsMessages"> | ||
| 28 | + INSERT INTO sms_message | ||
| 29 | + (template_id, pipeline, type, message_id, telephone, content, state, out_message_id, created_time, modified_time) | ||
| 30 | + VALUES | ||
| 31 | + <foreach collection="list" item="item" separator=","> | ||
| 32 | + (#{item.templateId}, #{item.pipeline}, #{item.type}, #{item.messageId}, #{item.telephone}, #{item.content}, #{item.state}, #{item.outMessageId}, #{item.createdTime}, #{item.modifiedTime}) | ||
| 33 | + </foreach> | ||
| 34 | + </insert> | ||
| 35 | + | ||
| 36 | + <select id="findSmsMessageById" parameterType="string" resultMap="SmsMessageMap"> | ||
| 37 | + SELECT * FROM sms_message WHERE message_id = #{messageId} | ||
| 38 | + </select> | ||
| 39 | + | ||
| 40 | + <update id="updateSmsMessageState" parameterType="com.diligrp.cashier.assistant.model.SmsMessageDO"> | ||
| 41 | + UPDATE sms_message SET state = #{state}, modified_time = #{modifiedTime} | ||
| 42 | + WHERE message_id = #{messageId} | ||
| 43 | + </update> | ||
| 44 | +</mapper> |
cashier-assistant/src/main/resources/com/diligrp/cashier/dao/mapper/SmsTemplateDao.xml
0 → 100644
| 1 | +++ a/cashier-assistant/src/main/resources/com/diligrp/cashier/dao/mapper/SmsTemplateDao.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.assistant.sms.dao.SmsTemplateDao"> | ||
| 6 | + <resultMap id="SmsTemplateMap" type="com.diligrp.cashier.assistant.model.SmsTemplateDO"> | ||
| 7 | + <id column="id" property="id"/> | ||
| 8 | + <result column="template_id" property="templateId"/> | ||
| 9 | + <result column="pipeline" property="pipeline"/> | ||
| 10 | + <result column="type" property="type"/> | ||
| 11 | + <result column="name" property="name"/> | ||
| 12 | + <result column="content" property="content"/> | ||
| 13 | + <result column="state" property="state"/> | ||
| 14 | + <result column="description" property="description"/> | ||
| 15 | + <result column="out_template_id" property="outTemplateId"/> | ||
| 16 | + <result column="version" property="version"/> | ||
| 17 | + <result column="created_time" property="createdTime"/> | ||
| 18 | + <result column="modified_time" property="modifiedTime"/> | ||
| 19 | + </resultMap> | ||
| 20 | + | ||
| 21 | + <insert id="insertSmsTemplate" parameterType="com.diligrp.cashier.assistant.model.SmsTemplateDO"> | ||
| 22 | + INSERT INTO sms_template | ||
| 23 | + (template_id, pipeline, type, name, content, state, description, out_template_id, version, created_time, modified_time) | ||
| 24 | + VALUES | ||
| 25 | + (#{templateId}, #{pipeline}, #{type}, #{name}, #{content}, #{state}, #{description}, #{outTemplateId}, #{version}, #{createdTime}, #{modifiedTime}) | ||
| 26 | + </insert> | ||
| 27 | + | ||
| 28 | + <select id="findSmsTemplateById" parameterType="string" resultMap="SmsTemplateMap"> | ||
| 29 | + SELECT * FROM sms_template WHERE template_id = #{templateId} | ||
| 30 | + </select> | ||
| 31 | + | ||
| 32 | + <update id="updateSmsTemplate" parameterType="com.diligrp.cashier.assistant.model.SmsTemplateDO"> | ||
| 33 | + UPDATE sms_template SET modified_time = #{modifiedTime} | ||
| 34 | + <if test="type != null"> | ||
| 35 | + , type = #{type} | ||
| 36 | + </if> | ||
| 37 | + <if test="name != null"> | ||
| 38 | + , name = #{name} | ||
| 39 | + </if> | ||
| 40 | + <if test="content != null"> | ||
| 41 | + , content = #{content} | ||
| 42 | + </if> | ||
| 43 | + <if test="state != null"> | ||
| 44 | + , state = #{state} | ||
| 45 | + </if> | ||
| 46 | + <if test="description != null"> | ||
| 47 | + , description = #{description} | ||
| 48 | + </if> | ||
| 49 | + WHERE template_id = #{templateId} | ||
| 50 | + </update> | ||
| 51 | + | ||
| 52 | + <update id="compareAndSetState" parameterType="com.diligrp.cashier.assistant.domain.TemplateStateDTO"> | ||
| 53 | + UPDATE sms_template SET state = #{state}, version = version + 1 | ||
| 54 | + <if test="modifiedTime != null"> | ||
| 55 | + , modified_time = #{modifiedTime} | ||
| 56 | + </if> | ||
| 57 | + WHERE | ||
| 58 | + template_id = #{templateId} AND version = #{version} | ||
| 59 | + </update> | ||
| 60 | + | ||
| 61 | + <update id="deleteSmsTemplate" parameterType="string"> | ||
| 62 | + DELETE FROM sms_template WHERE template_id = #{templateId} | ||
| 63 | + </update> | ||
| 64 | +</mapper> |
cashier-boss/build.gradle
0 → 100644
| 1 | +++ a/cashier-boss/build.gradle | ||
| 1 | +plugins { | ||
| 2 | + id 'java' | ||
| 3 | + id 'org.springframework.boot' | ||
| 4 | + id 'com.github.johnrengelman.shadow' | ||
| 5 | +} | ||
| 6 | + | ||
| 7 | +dependencies { | ||
| 8 | + implementation project(':cashier-trade') | ||
| 9 | + implementation project(':cashier-rtmart') | ||
| 10 | + implementation 'org.springframework.cloud:spring-cloud-starter' | ||
| 11 | + implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' | ||
| 12 | + implementation libs.spring.cloud.nacos.discovery | ||
| 13 | + implementation libs.spring.cloud.nacos.config | ||
| 14 | + // implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
| 15 | + // testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +springBoot { | ||
| 19 | + mainClass = 'com.diligrp.cashier.boss.CashierServiceBootstrap' | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +tasks.bootJar { | ||
| 23 | + archiveFileName = "cashier-service-${version}.jar" | ||
| 24 | + destinationDirectory = file("$rootProject.buildDir/libs") | ||
| 25 | +} | ||
| 0 | \ No newline at end of file | 26 | \ No newline at end of file |
cashier-boss/src/main/java/com/diligrp/cashier/boss/BossConfiguration.java
0 → 100644
| 1 | +++ a/cashier-boss/src/main/java/com/diligrp/cashier/boss/BossConfiguration.java | ||
| 1 | +package com.diligrp.cashier.boss; | ||
| 2 | + | ||
| 3 | +import org.springframework.context.annotation.ComponentScan; | ||
| 4 | +import org.springframework.context.annotation.Configuration; | ||
| 5 | + | ||
| 6 | +@Configuration | ||
| 7 | +@ComponentScan("com.diligrp.cashier.boss") | ||
| 8 | +public class BossConfiguration { | ||
| 9 | +} |
cashier-boss/src/main/java/com/diligrp/cashier/boss/CashierServiceBootstrap.java
0 → 100644
| 1 | +++ a/cashier-boss/src/main/java/com/diligrp/cashier/boss/CashierServiceBootstrap.java | ||
| 1 | +package com.diligrp.cashier.boss; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.assistant.AssistantConfiguration; | ||
| 4 | +import org.springframework.boot.SpringApplication; | ||
| 5 | +import org.springframework.boot.SpringBootConfiguration; | ||
| 6 | +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; | ||
| 7 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| 8 | +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; | ||
| 9 | +import org.springframework.context.annotation.Import; | ||
| 10 | + | ||
| 11 | +@SpringBootConfiguration | ||
| 12 | +@EnableAutoConfiguration | ||
| 13 | +@Import({BossConfiguration.class, AssistantConfiguration.class}) | ||
| 14 | +@EnableDiscoveryClient | ||
| 15 | +public class CashierServiceBootstrap { | ||
| 16 | + public static void main(String[] args) { | ||
| 17 | + SpringApplication.run(CashierServiceBootstrap.class, args); | ||
| 18 | + } | ||
| 19 | +} | ||
| 0 | \ No newline at end of file | 20 | \ No newline at end of file |
cashier-boss/src/main/resources/bootstrap.properties
0 → 100644
| 1 | +++ a/cashier-boss/src/main/resources/bootstrap.properties | ||
| 1 | +spring.profiles.active=dev | ||
| 2 | +spring.application.name=cashier-service | ||
| 3 | + | ||
| 4 | +spring.config.import[0]=nacos:${spring.application.name}.properties | ||
| 5 | +spring.config.import[1]=nacos:${spring.application.name}-${spring.profiles.active}.properties | ||
| 6 | + | ||
| 7 | +spring.cloud.nacos.discovery.enabled=true | ||
| 8 | +spring.cloud.nacos.discovery.group=MICROSERVICE | ||
| 9 | +spring.cloud.nacos.discovery.server-addr=nacos.diligrp.com:8848 | ||
| 10 | +spring.cloud.nacos.discovery.namespace=2267e673-b41f-458d-9643-2a03e4fd92fb | ||
| 11 | + | ||
| 12 | +spring.cloud.nacos.config.enabled=true | ||
| 13 | +spring.cloud.nacos.config.group=MICROSERVICE | ||
| 14 | +spring.cloud.nacos.config.server-addr=nacos.diligrp.com:8848 | ||
| 15 | +spring.cloud.nacos.config.namespace=2267e673-b41f-458d-9643-2a03e4fd92fb | ||
| 0 | \ No newline at end of file | 16 | \ No newline at end of file |
cashier-boss/src/main/resources/logback-spring.xml
0 → 100644
| 1 | +++ a/cashier-boss/src/main/resources/logback-spring.xml | ||
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<configuration scan="true" scanPeriod="30 seconds" debug="false"> | ||
| 3 | + <property name="LOG_NAME" value="cashier-service" /> | ||
| 4 | + | ||
| 5 | + <property name="APP_BASE_DIR" value="${user.dir}" /> | ||
| 6 | + | ||
| 7 | + <property name="LOG_HOME" value="${APP_BASE_DIR}/logs" /> | ||
| 8 | + | ||
| 9 | + <property name="LOG_HISTORY_DIR" value="${LOG_HOME}/history" /> | ||
| 10 | + | ||
| 11 | + <property name="LOG_CHARSET" value="UTF-8" /> | ||
| 12 | + | ||
| 13 | + <property name="ASYNC_QUEUE_SIZE" value="1024" /> | ||
| 14 | + | ||
| 15 | + <springProperty scope="context" name="profile" source="spring.profiles.active" defaultValue="dev" /> | ||
| 16 | + | ||
| 17 | + <property name="LOG_PATTERN" value="%d %-5level [${LOG_NAME}-${profile}] [%t] [%c:%L] -| %msg%n" /> | ||
| 18 | + | ||
| 19 | + <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> | ||
| 20 | + | ||
| 21 | + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | ||
| 22 | + <target>System.out</target> | ||
| 23 | + <encoder> | ||
| 24 | + <charset>${LOG_CHARSET}</charset> | ||
| 25 | + <pattern>${LOG_PATTERN}</pattern> | ||
| 26 | + </encoder> | ||
| 27 | + <!-- 过滤器:只输出INFO及以上级别,减少控制台冗余 --> | ||
| 28 | + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | ||
| 29 | + <level>INFO</level> | ||
| 30 | + </filter> | ||
| 31 | + </appender> | ||
| 32 | + | ||
| 33 | + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
| 34 | + <file>${LOG_HOME}/${LOG_NAME}.log</file> | ||
| 35 | + <append>true</append> | ||
| 36 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | ||
| 37 | + <!-- 日志命名:按天分类,按大小分卷,自动压缩为zip --> | ||
| 38 | + <fileNamePattern>${LOG_HISTORY_DIR}/${LOG_NAME}_%d{yyyy-MM-dd}_%i.log.zip</fileNamePattern> | ||
| 39 | + <!-- 日志保留30天,自动清理过期日志 --> | ||
| 40 | + <maxHistory>30</maxHistory> | ||
| 41 | + <!-- 单个日志文件最大60MB(减少切割次数) --> | ||
| 42 | + <maxFileSize>60MB</maxFileSize> | ||
| 43 | + <!-- 总日志大小上限10GB,防止磁盘占满 --> | ||
| 44 | + <totalSizeCap>10GB</totalSizeCap> | ||
| 45 | + </rollingPolicy> | ||
| 46 | + <encoder> | ||
| 47 | + <charset>${LOG_CHARSET}</charset> | ||
| 48 | + <pattern>${LOG_PATTERN}</pattern> | ||
| 49 | + </encoder> | ||
| 50 | + <!-- 过滤器:只输出DEBUG及以上级别 --> | ||
| 51 | + <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> | ||
| 52 | + <level>DEBUG</level> | ||
| 53 | + </filter> | ||
| 54 | + </appender> | ||
| 55 | + | ||
| 56 | + <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender"> | ||
| 57 | + <appender-ref ref="FILE" /> | ||
| 58 | + <!-- 队列大小:根据业务并发调整 --> | ||
| 59 | + <queueSize>${ASYNC_QUEUE_SIZE}</queueSize> | ||
| 60 | + <!-- 超出队列时不阻塞业务线程(生产环境关键) --> | ||
| 61 | + <neverBlock>true</neverBlock> | ||
| 62 | + <!-- 丢弃策略:超出队列时丢弃TRACE/DEBUG级日志 --> | ||
| 63 | + <discardingThreshold>0</discardingThreshold> | ||
| 64 | + <!-- 包含调用者数据,避免异步日志丢失异常栈 --> | ||
| 65 | + <includeCallerData>true</includeCallerData> | ||
| 66 | + </appender> | ||
| 67 | + | ||
| 68 | + <!-- ************************ 多环境日志配置 ************************ --> | ||
| 69 | + <!-- 开发环境:仅控制台输出 --> | ||
| 70 | + <springProfile name="dev"> | ||
| 71 | + <root level="INFO"> | ||
| 72 | + <appender-ref ref="CONSOLE" /> | ||
| 73 | + </root> | ||
| 74 | + | ||
| 75 | + <logger name="com.diligrp.cashier" level="DEBUG" additivity="false"> | ||
| 76 | + <appender-ref ref="CONSOLE" /> | ||
| 77 | + </logger> | ||
| 78 | + | ||
| 79 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | ||
| 80 | + <appender-ref ref="CONSOLE" /> | ||
| 81 | + </logger> | ||
| 82 | + </springProfile> | ||
| 83 | + | ||
| 84 | + <!-- 测试环境:控制台+异步文件输出 --> | ||
| 85 | + <springProfile name="test"> | ||
| 86 | + <root level="INFO"> | ||
| 87 | + <appender-ref ref="CONSOLE" /> | ||
| 88 | + </root> | ||
| 89 | + | ||
| 90 | + <logger name="com.diligrp.cashier" level="DEBUG" additivity="false"> | ||
| 91 | + <appender-ref ref="ASYNC_FILE" /> | ||
| 92 | + </logger> | ||
| 93 | + | ||
| 94 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | ||
| 95 | + <appender-ref ref="CONSOLE" /> | ||
| 96 | + <appender-ref ref="ASYNC_FILE" /> | ||
| 97 | + </logger> | ||
| 98 | + </springProfile> | ||
| 99 | + | ||
| 100 | + <!-- 灰度/生产环境:控制台+异步文件输出 --> | ||
| 101 | + <springProfile name="pre,prod"> | ||
| 102 | + <root level="INFO"> | ||
| 103 | + <appender-ref ref="CONSOLE" /> | ||
| 104 | + </root> | ||
| 105 | + | ||
| 106 | + <logger name="com.diligrp.cashier" level="DEBUG" additivity="false"> | ||
| 107 | + <appender-ref ref="ASYNC_FILE" /> | ||
| 108 | + </logger> | ||
| 109 | + | ||
| 110 | + <logger name="com.alibaba" level="ERROR" additivity="false"> | ||
| 111 | + <appender-ref ref="CONSOLE" /> | ||
| 112 | + <appender-ref ref="ASYNC_FILE" /> | ||
| 113 | + </logger> | ||
| 114 | + </springProfile> | ||
| 115 | + | ||
| 116 | + <!-- ************************ 通用框架日志配置 ************************ --> | ||
| 117 | + <logger name="org.springframework" level="WARN" additivity="false"> | ||
| 118 | + <appender-ref ref="CONSOLE" /> | ||
| 119 | + </logger> | ||
| 120 | + | ||
| 121 | + <logger name="org.mybatis" level="WARN" additivity="false"> | ||
| 122 | + <appender-ref ref="CONSOLE" /> | ||
| 123 | + </logger> | ||
| 124 | +</configuration> | ||
| 0 | \ No newline at end of file | 125 | \ No newline at end of file |
cashier-pipeline/build.gradle
0 → 100644
cashier-rtmart/build.gradle
0 → 100644
cashier-shared/build.gradle
0 → 100644
| 1 | +++ a/cashier-shared/build.gradle | ||
| 1 | +dependencies { | ||
| 2 | + //api 'org.springframework.boot:spring-boot-starter-data-mongodb' | ||
| 3 | + api libs.mybatis.starter | ||
| 4 | + api 'org.springframework.boot:spring-boot-starter-data-redis' | ||
| 5 | + //api 'org.redisson:redisson-spring-boot-starter:3.52.0' | ||
| 6 | + api 'org.springframework.boot:spring-boot-starter-amqp' | ||
| 7 | + api 'org.springframework.cloud:spring-cloud-starter-loadbalancer' | ||
| 8 | + api 'org.springframework.cloud:spring-cloud-starter-openfeign' | ||
| 9 | + api libs.cache.caffeine | ||
| 10 | + | ||
| 11 | + runtimeOnly libs.mysql.driver | ||
| 12 | +} | ||
| 0 | \ No newline at end of file | 13 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/Constants.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/Constants.java | ||
| 1 | +package com.diligrp.cashier.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 | + | ||
| 16 | + public final static String CONTENT_TYPE = "application/json;charset=UTF-8"; | ||
| 17 | +} | ||
| 0 | \ No newline at end of file | 18 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/ErrorCode.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/ErrorCode.java | ||
| 1 | +package com.diligrp.cashier.shared; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 系统错误码列表 - 错误码前三位用于区分模块 | ||
| 5 | + */ | ||
| 6 | +public class ErrorCode { | ||
| 7 | + // 系统未知异常 | ||
| 8 | + public static final int SYSTEM_UNKNOWN_ERROR = 300000; | ||
| 9 | + // 无效参数错误 | ||
| 10 | + public static final int ILLEGAL_ARGUMENT_ERROR = 300001; | ||
| 11 | + // 访问未授权 | ||
| 12 | + public static final int UNAUTHORIZED_ACCESS_ERROR = 300002; | ||
| 13 | + // 操作不允许 | ||
| 14 | + public static final int OPERATION_NOT_ALLOWED = 300003; | ||
| 15 | + // 对象不存在 | ||
| 16 | + public static final int OBJECT_NOT_FOUND = 300004; | ||
| 17 | + // 对象已存在 | ||
| 18 | + public static final int OBJECT_ALREADY_EXISTS = 300005; | ||
| 19 | + // 数据并发修改 | ||
| 20 | + public static final int SYSTEM_BUSY_ERROR = 301000; | ||
| 21 | + // 无效对象状态 | ||
| 22 | + public static final int INVALID_OBJECT_STATE = 301001; | ||
| 23 | + // 远程服务访问错误 | ||
| 24 | + public static final int SERVICE_ACCESS_ERROR = 301002; | ||
| 25 | + | ||
| 26 | + public static final String MESSAGE_UNKNOWN_ERROR = "收银台系统未知异常,请联系管理员"; | ||
| 27 | + | ||
| 28 | + public static final String MESSAGE_ACCESS_DENIED = "未授权的系统访问"; | ||
| 29 | + | ||
| 30 | + public static final String MESSAGE_SYSTEM_BUSY = "收银台系统正忙,请稍后重试"; | ||
| 31 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/SharedConfiguration.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/SharedConfiguration.java | ||
| 1 | +package com.diligrp.cashier.shared; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.exception.PlatformServiceException; | ||
| 4 | +import com.diligrp.cashier.shared.security.RsaCipher; | ||
| 5 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 6 | +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||
| 7 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | ||
| 8 | +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; | ||
| 9 | +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; | ||
| 10 | +import org.springframework.context.annotation.Bean; | ||
| 11 | +import org.springframework.context.annotation.ComponentScan; | ||
| 12 | +import org.springframework.context.annotation.Configuration; | ||
| 13 | +import org.springframework.core.convert.converter.Converter; | ||
| 14 | +import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| 15 | +import org.springframework.data.redis.core.RedisTemplate; | ||
| 16 | +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | ||
| 17 | +import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
| 18 | +import org.springframework.stereotype.Component; | ||
| 19 | +import org.springframework.util.StringUtils; | ||
| 20 | +import org.springframework.web.servlet.config.annotation.CorsRegistry; | ||
| 21 | +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
| 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.cashier.shared") | ||
| 34 | +public class SharedConfiguration { | ||
| 35 | + @Bean | ||
| 36 | + public WebMvcConfigurer corsConfigurer() { | ||
| 37 | + return new WebMvcConfigurer() { | ||
| 38 | + @Override | ||
| 39 | + public void addCorsMappings(CorsRegistry registry) { | ||
| 40 | + registry.addMapping("/**") | ||
| 41 | + .allowedOriginPatterns("*") | ||
| 42 | + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") | ||
| 43 | + .allowedHeaders("*") | ||
| 44 | + .allowCredentials(true) | ||
| 45 | + .maxAge(3600); | ||
| 46 | + } | ||
| 47 | + }; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Bean | ||
| 51 | + public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { | ||
| 52 | + RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); | ||
| 53 | + template.setConnectionFactory(factory); | ||
| 54 | + template.setKeySerializer(new StringRedisSerializer()); | ||
| 55 | + template.setHashKeySerializer(new StringRedisSerializer()); | ||
| 56 | + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); | ||
| 57 | + template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); | ||
| 58 | + template.afterPropertiesSet(); | ||
| 59 | + return template; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + // Jackson DataBinding所有配置 | ||
| 63 | + @Bean | ||
| 64 | + @ConditionalOnClass(JavaTimeModule.class) | ||
| 65 | + public Jackson2ObjectMapperBuilderCustomizer customizeJacksonConfig() { | ||
| 66 | + return JsonUtils::initObjectMapperBuilder; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + @Bean | ||
| 70 | + public Converter<String, LocalDateTime> localDateTimeConverter() { | ||
| 71 | + // 不能使用lambda表达式,否则导致springboot启动问题 | ||
| 72 | + return new Converter<String, LocalDateTime>() { | ||
| 73 | + @Override | ||
| 74 | + public LocalDateTime convert(String source) { | ||
| 75 | + try { | ||
| 76 | + return StringUtils.hasText(source) ? LocalDateTime.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT)) : null; | ||
| 77 | + } catch (Exception ex) { | ||
| 78 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalDateTime", source), ex); | ||
| 79 | + } | ||
| 80 | + } | ||
| 81 | + }; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @Bean | ||
| 85 | + public Converter<String, LocalDate> localDateConverter() { | ||
| 86 | + // 不能使用lambda表达式,否则导致springboot启动问题 | ||
| 87 | + return new Converter<String, LocalDate>() { | ||
| 88 | + @Override | ||
| 89 | + public LocalDate convert(String source) { | ||
| 90 | + try { | ||
| 91 | + return StringUtils.hasText(source) ? LocalDate.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_FORMAT)) : null; | ||
| 92 | + } catch (Exception ex) { | ||
| 93 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalDate", source), ex); | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + }; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + @Bean | ||
| 100 | + public Converter<String, LocalTime> localTimeConverter() { | ||
| 101 | + // 不能使用lambda表达式,否则导致springboot启动问题 | ||
| 102 | + return new Converter<String, LocalTime>() { | ||
| 103 | + @Override | ||
| 104 | + public LocalTime convert(String source) { | ||
| 105 | + try { | ||
| 106 | + return StringUtils.hasText(source) ? LocalTime.parse(source, DateTimeFormatter.ofPattern(Constants.TIME_FORMAT)) : null; | ||
| 107 | + } catch (Exception ex) { | ||
| 108 | + throw new IllegalArgumentException(String.format("Error parse %s to LocalTime", source), ex); | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + }; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + @Bean | ||
| 115 | + public Converter<String, Date> dateConverter() { | ||
| 116 | + // 不能使用lambda表达式,否则导致springboot启动问题 | ||
| 117 | + return new Converter<String, Date>() { | ||
| 118 | + @Override | ||
| 119 | + public Date convert(String source) { | ||
| 120 | + try { | ||
| 121 | + return StringUtils.hasText(source) ? new SimpleDateFormat(Constants.DATE_TIME_FORMAT).parse(source) : null; | ||
| 122 | + } catch (Exception ex) { | ||
| 123 | + throw new IllegalArgumentException(String.format("Error parse %s to Date", source), ex); | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + }; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + @Component | ||
| 130 | + @ConfigurationPropertiesBinding | ||
| 131 | + public class PrivateKeyConverter implements Converter<String, PrivateKey> { | ||
| 132 | + @Override | ||
| 133 | + public PrivateKey convert(String source) { | ||
| 134 | + try { | ||
| 135 | + return RsaCipher.getPrivateKey(source); | ||
| 136 | + } catch (Exception ex) { | ||
| 137 | + throw new PlatformServiceException("privateKey configuration failed", ex); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + @Component | ||
| 143 | + @ConfigurationPropertiesBinding | ||
| 144 | + public class PublicKeyConverter implements Converter<String, PublicKey> { | ||
| 145 | + @Override | ||
| 146 | + public PublicKey convert(String source) { | ||
| 147 | + try { | ||
| 148 | + return RsaCipher.getPublicKey(source); | ||
| 149 | + } catch (Exception ex) { | ||
| 150 | + throw new PlatformServiceException("publicKey configuration failed", ex); | ||
| 151 | + } | ||
| 152 | + } | ||
| 153 | + } | ||
| 154 | +} | ||
| 0 | \ No newline at end of file | 155 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/ByteDecoder.java
0 → 100644
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/ByteEncoder.java
0 → 100644
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/StringCodec.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/StringCodec.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/BaseDO.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/BaseDO.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/ContainerSupport.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/ContainerSupport.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/Message.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/Message.java | ||
| 1 | +package com.diligrp.cashier.shared.domain; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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 | \ No newline at end of file | 70 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/PageMessage.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/PageMessage.java | ||
| 1 | +package com.diligrp.cashier.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 | + return failure(CODE_FAILURE, message); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public static PageMessage<?> failure(int code, String message) { | ||
| 34 | + PageMessage<?> page = new PageMessage<>(); | ||
| 35 | + page.setCode(code); | ||
| 36 | + page.setTotal(0); | ||
| 37 | + page.setData(null); | ||
| 38 | + page.setMessage(message); | ||
| 39 | + return page; | ||
| 40 | + } | ||
| 41 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/PageQuery.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/domain/PageQuery.java | ||
| 1 | +package com.diligrp.cashier.shared.domain; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 分页查询领域模型 | ||
| 5 | + */ | ||
| 6 | +public class PageQuery { | ||
| 7 | + private Integer pageNo = 1; | ||
| 8 | + // 每页记录数 | ||
| 9 | + private Integer pageSize = 20; | ||
| 10 | + | ||
| 11 | + // 起始行下标 | ||
| 12 | + protected Integer start; | ||
| 13 | + // 获取的记录行数 | ||
| 14 | + protected Integer limit; | ||
| 15 | + | ||
| 16 | + public Integer getStart() { | ||
| 17 | + return start; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public void setStart(Integer start) { | ||
| 21 | + this.start = start; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public Integer getLimit() { | ||
| 25 | + return limit; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public void setLimit(Integer limit) { | ||
| 29 | + this.limit = limit; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public Integer getPageNo() { | ||
| 33 | + return pageNo; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public void setPageNo(Integer pageNo) { | ||
| 37 | + this.pageNo = pageNo; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public Integer getPageSize() { | ||
| 41 | + return pageSize; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public void setPageSize(Integer pageSize) { | ||
| 45 | + this.pageSize = pageSize; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 通过页号、每页记录数计算起始行下标 | ||
| 50 | + */ | ||
| 51 | + public void from(int pageNo, int pageSize) { | ||
| 52 | + this.start = (pageNo - 1) * pageSize; | ||
| 53 | + this.limit = pageSize; | ||
| 54 | + } | ||
| 55 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/DefaultExceptionHandler.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/DefaultExceptionHandler.java | ||
| 1 | +package com.diligrp.cashier.shared.exception; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 4 | +import com.diligrp.cashier.shared.domain.Message; | ||
| 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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/PlatformServiceException.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/PlatformServiceException.java | ||
| 1 | +package com.diligrp.cashier.shared.exception; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceAccessException.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceAccessException.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceConnectException.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceConnectException.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceTimeoutException.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/exception/ServiceTimeoutException.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/mybatis/GenericEnumTypeHandler.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/mybatis/GenericEnumTypeHandler.java | ||
| 1 | +package com.diligrp.cashier.shared.mybatis; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/mybatis/MybatisMapperSupport.java
0 → 100644
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/AesCipher.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/AesCipher.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/HexUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/HexUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/KeyStoreUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/KeyStoreUtils.java | ||
| 1 | +package com.diligrp.cashier.shared.security; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/Md5Cipher.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/Md5Cipher.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/PasswordUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/PasswordUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/PbeCipher.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/PbeCipher.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/RsaCipher.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/RsaCipher.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/ShaCipher.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/security/ShaCipher.java | ||
| 1 | +package com.diligrp.cashier.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-256"; | ||
| 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 | + | ||
| 18 | + public static byte[] encrypt(byte[] data, String algorithm) throws Exception { | ||
| 19 | + MessageDigest sha = MessageDigest.getInstance(algorithm); | ||
| 20 | + sha.update(data); | ||
| 21 | + | ||
| 22 | + return sha.digest(); | ||
| 23 | + } | ||
| 24 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ILifeCycle.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ILifeCycle.java | ||
| 1 | +package com.diligrp.cashier.shared.service; | ||
| 2 | + | ||
| 3 | +public interface ILifeCycle | ||
| 4 | +{ | ||
| 5 | + void start() throws Exception; | ||
| 6 | + | ||
| 7 | + void stop() throws Exception; | ||
| 8 | + | ||
| 9 | + boolean isRunning(); | ||
| 10 | + | ||
| 11 | + boolean isStarted(); | ||
| 12 | + | ||
| 13 | + String getState(); | ||
| 14 | +} | ||
| 0 | \ No newline at end of file | 15 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/service/LifeCycle.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/service/LifeCycle.java | ||
| 1 | +package com.diligrp.cashier.shared.service; | ||
| 2 | + | ||
| 3 | +public abstract class LifeCycle implements ILifeCycle { | ||
| 4 | + private final static int FAILED = -1; | ||
| 5 | + private final static int STOPPED = 0; | ||
| 6 | + private final static int STARTING = 1; | ||
| 7 | + private final static int STARTED = 2; | ||
| 8 | + private final static int STOPPING = 3; | ||
| 9 | + | ||
| 10 | + private final Object lock = new Object(); | ||
| 11 | + private volatile int state = STOPPED; | ||
| 12 | + | ||
| 13 | + @Override | ||
| 14 | + public void start() throws Exception { | ||
| 15 | + // Double check | ||
| 16 | + if (state == STARTED || state == STARTING) { | ||
| 17 | + return; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + synchronized (lock) { | ||
| 21 | + try { | ||
| 22 | + if (state == STARTED || state == STARTING) { | ||
| 23 | + return; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + setState(STARTING); | ||
| 27 | + doStart(); | ||
| 28 | + setState(STARTED); | ||
| 29 | + } catch (Throwable ex) { | ||
| 30 | + setState(FAILED); | ||
| 31 | + throw new Exception("Start the component exception", ex); | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + @Override | ||
| 37 | + public void stop() throws Exception { | ||
| 38 | + // Double check | ||
| 39 | + if (state == STOPPING || state == STOPPED) { | ||
| 40 | + return; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + synchronized (lock) { | ||
| 44 | + try { | ||
| 45 | + if (state == STOPPING || state == STOPPED) { | ||
| 46 | + return; | ||
| 47 | + } | ||
| 48 | + setState(STOPPING); | ||
| 49 | + doStop(); | ||
| 50 | + setState(STOPPED); | ||
| 51 | + } catch (Throwable ex) { | ||
| 52 | + setState(FAILED); | ||
| 53 | + throw new Exception("Stop the component exception", ex); | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + protected void doStart() throws Exception { | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + protected void doStop() throws Exception { | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + @Override | ||
| 65 | + public boolean isRunning() { | ||
| 66 | + final int _state = state; | ||
| 67 | + return _state == STARTED || _state == STARTING; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Override | ||
| 71 | + public boolean isStarted() { | ||
| 72 | + return state == STARTED; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + @Override | ||
| 76 | + public String getState() { | ||
| 77 | + switch (state) { | ||
| 78 | + case STARTED: | ||
| 79 | + return "started"; | ||
| 80 | + case STOPPED: | ||
| 81 | + return "stopped"; | ||
| 82 | + case STARTING: | ||
| 83 | + return "starting"; | ||
| 84 | + case STOPPING: | ||
| 85 | + return "stopping"; | ||
| 86 | + case FAILED: | ||
| 87 | + return "failed"; | ||
| 88 | + default: | ||
| 89 | + return "unknown"; | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + private void setState(int state) { | ||
| 94 | + this.state = state; | ||
| 95 | + } | ||
| 96 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ServiceEndpointSupport.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ServiceEndpointSupport.java | ||
| 1 | +package com.diligrp.cashier.shared.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.exception.ServiceAccessException; | ||
| 4 | +import com.diligrp.cashier.shared.exception.ServiceConnectException; | ||
| 5 | +import com.diligrp.cashier.shared.exception.ServiceTimeoutException; | ||
| 6 | +import com.diligrp.cashier.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.*; | ||
| 17 | +import java.util.concurrent.Executor; | ||
| 18 | + | ||
| 19 | +public abstract class ServiceEndpointSupport { | ||
| 20 | + protected static final int MAX_CONNECT_TIMEOUT_TIME = 10000; | ||
| 21 | + | ||
| 22 | + protected static final int MAX_REQUEST_TIMEOUT_TIME = 30000; | ||
| 23 | + | ||
| 24 | + protected static final String CONTENT_TYPE = "Content-Type"; | ||
| 25 | + | ||
| 26 | + protected static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8"; | ||
| 27 | + | ||
| 28 | + protected static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded;charset=UTF-8"; | ||
| 29 | + | ||
| 30 | + protected static final String CONTENT_TYPE_XML = "text/xml;charset=UTF-8"; | ||
| 31 | + | ||
| 32 | + protected volatile HttpClient httpClient; | ||
| 33 | + | ||
| 34 | + protected final Object lock = new Object(); | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 38 | + */ | ||
| 39 | + public HttpResult send(String requestUrl, String body) { | ||
| 40 | + return send(requestUrl, null, body); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 45 | + */ | ||
| 46 | + public HttpResult send(String requestUrl, HttpHeader[] headers, String body) { | ||
| 47 | + if (ObjectUtils.isEmpty(requestUrl)) { | ||
| 48 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | ||
| 52 | + .version(HttpClient.Version.HTTP_2) | ||
| 53 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | ||
| 54 | + .header(CONTENT_TYPE, CONTENT_TYPE_JSON) | ||
| 55 | + .POST(HttpRequest.BodyPublishers.ofString(body)); | ||
| 56 | + // Wrap the HTTP headers | ||
| 57 | + if (headers != null && headers.length > 0) { | ||
| 58 | + // request.headers(Arrays.stream(headers).flatMap(h -> Stream.of(h.param, h.value)).toArray(String[]::new)); | ||
| 59 | + Arrays.stream(headers).filter(Objects::nonNull).forEach(h -> request.header(h.param, h.value)); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + return execute(request.build()); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 67 | + */ | ||
| 68 | + public HttpResult send(String requestUrl, HttpParam[] params) { | ||
| 69 | + return send(requestUrl, null, params); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + /** | ||
| 73 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 74 | + */ | ||
| 75 | + public HttpResult send(String requestUrl, HttpHeader[] headers, HttpParam[] params) { | ||
| 76 | + if (ObjectUtils.isEmpty(requestUrl)) { | ||
| 77 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | ||
| 81 | + .version(HttpClient.Version.HTTP_2) | ||
| 82 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | ||
| 83 | + .header(CONTENT_TYPE, CONTENT_TYPE_FORM); | ||
| 84 | + // Wrap the HTTP headers | ||
| 85 | + if (headers != null && headers.length > 0) { | ||
| 86 | + Arrays.stream(headers).filter(Objects::nonNull).forEach(h -> request.header(h.param, h.value)); | ||
| 87 | + } | ||
| 88 | + if (params != null && params.length > 0) { | ||
| 89 | + // [key1, value1, key2, value2] -> key1=value1&key2=value2 | ||
| 90 | + String body = Arrays.stream(params).filter(Objects::nonNull) | ||
| 91 | + .map(p -> "".concat(p.param).concat("=").concat(URLEncoder.encode(p.value, StandardCharsets.UTF_8))) | ||
| 92 | + .reduce((value1, value2) -> "".concat(value1).concat("&").concat(value2)).orElse(""); | ||
| 93 | + request.POST(HttpRequest.BodyPublishers.ofString(body)); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + return execute(request.build()); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 100 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 101 | + */ | ||
| 102 | + public HttpResult sendXml(String requestUrl, HttpHeader[] headers, String xml) { | ||
| 103 | + if (ObjectUtils.isEmpty(requestUrl)) { | ||
| 104 | + throw new IllegalArgumentException("Invalid http request url, url=" + requestUrl); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + HttpRequest.Builder request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) | ||
| 108 | + .version(HttpClient.Version.HTTP_2) | ||
| 109 | + .timeout(Duration.ofMillis(MAX_REQUEST_TIMEOUT_TIME)) | ||
| 110 | + .header(CONTENT_TYPE, CONTENT_TYPE_XML) | ||
| 111 | + .POST(HttpRequest.BodyPublishers.ofString(xml)); | ||
| 112 | + // Wrap the HTTP headers | ||
| 113 | + if (headers != null && headers.length > 0) { | ||
| 114 | + Arrays.stream(headers).filter(Objects::nonNull).forEach(h -> request.header(h.param, h.value)); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + return execute(request.build()); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + protected HttpClient getHttpClient() { | ||
| 121 | + if (httpClient == null) { | ||
| 122 | + synchronized (lock) { | ||
| 123 | + // Double check for performance purpose | ||
| 124 | + if (httpClient == null) { | ||
| 125 | + HttpClient.Builder builder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2) | ||
| 126 | + // 认证,默认情况下Authenticator.getDefault()是null值,会报错 | ||
| 127 | +// .authenticator(Authenticator.getDefault()) | ||
| 128 | + // 缓存,默认情况下 CookieHandler.getDefault()是null值,会报错 | ||
| 129 | +// .cookieHandler(CookieHandler.getDefault()) | ||
| 130 | + .connectTimeout(Duration.ofMillis(MAX_CONNECT_TIMEOUT_TIME)) | ||
| 131 | + .followRedirects(HttpClient.Redirect.NEVER); | ||
| 132 | + // Build SSL | ||
| 133 | + Optional<SSLContext> sslContext = buildSSLContext(); | ||
| 134 | + sslContext.ifPresent(builder::sslContext); | ||
| 135 | + Optional<SSLParameters> parameters = buildSslParameters(); | ||
| 136 | + parameters.ifPresent(builder::sslParameters); | ||
| 137 | + // Build thread pool | ||
| 138 | + Optional<Executor> executor = buildThreadPool(); | ||
| 139 | + executor.ifPresent(builder::executor); | ||
| 140 | + httpClient = builder.build(); | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + return httpClient; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + protected Optional<SSLContext> buildSSLContext() { | ||
| 149 | + return Optional.empty(); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + protected Optional<SSLParameters> buildSslParameters() { | ||
| 153 | + return Optional.empty(); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + protected Optional<Executor> buildThreadPool() { | ||
| 157 | + return Optional.empty(); | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + /** | ||
| 161 | + * @throws ServiceConnectException, ServiceAccessException, ServiceTimeoutException | ||
| 162 | + */ | ||
| 163 | + protected HttpResult execute(HttpRequest request) { | ||
| 164 | + try { | ||
| 165 | + HttpResponse<String> response = getHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); | ||
| 166 | + | ||
| 167 | + HttpResult result = HttpResult.create(); | ||
| 168 | + result.statusCode = response.statusCode(); | ||
| 169 | + result.responseText = response.body(); | ||
| 170 | + result.headers = response.headers() == null ? null : response.headers().map(); | ||
| 171 | + return result; | ||
| 172 | + } catch (ConnectException | HttpConnectTimeoutException cex) { | ||
| 173 | + throw new ServiceConnectException("Remote service connection exception", cex); | ||
| 174 | + } catch (HttpTimeoutException hex) { | ||
| 175 | + throw new ServiceTimeoutException("Remote service access timeout", hex); | ||
| 176 | + } catch (Exception ex) { | ||
| 177 | + throw new ServiceAccessException("Remote service access exception", ex); | ||
| 178 | + } | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + public static class HttpParam { | ||
| 182 | + public String param; | ||
| 183 | + public String value; | ||
| 184 | + | ||
| 185 | + private HttpParam(String param, String value) { | ||
| 186 | + this.param = param; | ||
| 187 | + this.value = value; | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + public static HttpParam create(String param, String value) { | ||
| 191 | + return new HttpParam(param, value); | ||
| 192 | + } | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + public static class HttpHeader { | ||
| 196 | + public String param; | ||
| 197 | + public String value; | ||
| 198 | + | ||
| 199 | + private HttpHeader(String param, String value) { | ||
| 200 | + this.param = param; | ||
| 201 | + this.value = value; | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + public static HttpHeader create(String param, String value) { | ||
| 205 | + return new HttpHeader(param, value); | ||
| 206 | + } | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + public static class HttpResult { | ||
| 210 | + public int statusCode = -1; | ||
| 211 | + public String responseText; | ||
| 212 | + public Map<String, List<String>> headers; | ||
| 213 | + | ||
| 214 | + public static HttpResult create() { | ||
| 215 | + return new HttpResult(); | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + public String header(String key) { | ||
| 219 | + if (headers == null) { | ||
| 220 | + return null; | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + List<String> values = headers.get(key); | ||
| 224 | + if (values == null || values.isEmpty()) { | ||
| 225 | + return null; | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + return values.getFirst(); | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ThreadPoolService.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/service/ThreadPoolService.java | ||
| 1 | +package com.diligrp.cashier.shared.service; | ||
| 2 | + | ||
| 3 | +import java.util.concurrent.ExecutorService; | ||
| 4 | +import java.util.concurrent.LinkedBlockingQueue; | ||
| 5 | +import java.util.concurrent.ThreadPoolExecutor; | ||
| 6 | +import java.util.concurrent.TimeUnit; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 请谨慎使用此线程池工具类,通常建议根据特定的使用场景设置线程池参数,不建议使用统一的线程池配置 | ||
| 10 | + * JDK的线程池类并不能很好区分"计算密集型"和"IO密集型"任务类型,并根据不同的任务类型去配置不同的参数 | ||
| 11 | + */ | ||
| 12 | +public final class ThreadPoolService { | ||
| 13 | + | ||
| 14 | + private static final int CPU_CORE_NUM = Runtime.getRuntime().availableProcessors(); | ||
| 15 | + | ||
| 16 | + private static final int CPU_MAX_POOL_SIZE = 100; | ||
| 17 | + | ||
| 18 | + private static final int IO_MAX_POOL_SIZE = 1000; | ||
| 19 | + | ||
| 20 | + // CPU运算密集型任务的线程池实例 | ||
| 21 | + private static volatile ExecutorService cpuThreadPoll; | ||
| 22 | + | ||
| 23 | + // IO密集型任务的线程池实例 | ||
| 24 | + private static volatile ExecutorService ioThreadPoll; | ||
| 25 | + | ||
| 26 | + private ThreadPoolService() { | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * 获取运算密集型任务的线程池实例 | ||
| 31 | + * 通常建议根据特定的使用场景设置线程池参数,不建议使用统一的线程池配置 | ||
| 32 | + */ | ||
| 33 | + public static ExecutorService getCpuThreadPoll() { | ||
| 34 | + if (cpuThreadPoll == null) { | ||
| 35 | + synchronized (ThreadPoolService.class) { | ||
| 36 | + if (cpuThreadPoll == null) { | ||
| 37 | + cpuThreadPoll = new ThreadPoolExecutor(CPU_CORE_NUM + 1, CPU_MAX_POOL_SIZE, | ||
| 38 | + 20, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), | ||
| 39 | + new ThreadPoolExecutor.AbortPolicy()); | ||
| 40 | + } | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + return cpuThreadPoll; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 获取IO密集型任务的线程池实例 | ||
| 48 | + * 通常建议根据特定的使用场景设置线程池参数,不建议使用统一的线程池配置 | ||
| 49 | + */ | ||
| 50 | + public static ExecutorService getIoThreadPoll() { | ||
| 51 | + if (ioThreadPoll == null) { | ||
| 52 | + synchronized (ThreadPoolService.class) { | ||
| 53 | + if (ioThreadPoll == null) { | ||
| 54 | + ioThreadPoll = new ThreadPoolExecutor(CPU_CORE_NUM + 1, IO_MAX_POOL_SIZE, | ||
| 55 | + 20, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), | ||
| 56 | + new ThreadPoolExecutor.AbortPolicy()); | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + return ioThreadPoll; | ||
| 61 | + } | ||
| 62 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/type/Gender.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/type/Gender.java | ||
| 1 | +package com.diligrp.cashier.shared.type; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 4 | +import com.diligrp.cashier.shared.exception.PlatformServiceException; | ||
| 5 | + | ||
| 6 | +import java.util.Arrays; | ||
| 7 | +import java.util.List; | ||
| 8 | +import java.util.Optional; | ||
| 9 | +import java.util.stream.Stream; | ||
| 10 | + | ||
| 11 | +public enum Gender implements IEnumType { | ||
| 12 | + | ||
| 13 | + MALE("男", 1), | ||
| 14 | + | ||
| 15 | + FEMALE("女", 2); | ||
| 16 | + | ||
| 17 | + private String name; | ||
| 18 | + private int code; | ||
| 19 | + | ||
| 20 | + Gender(String name, int code) { | ||
| 21 | + this.name = name; | ||
| 22 | + this.code = code; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public static Optional<Gender> getGender(int code) { | ||
| 26 | + Stream<Gender> GENDERS = Arrays.stream(values()); | ||
| 27 | + return GENDERS.filter((gender) -> gender.getCode() == code).findFirst(); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public static Gender getByCode(int code) { | ||
| 31 | + return getGender(code).orElseThrow(() -> new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "invalid gender")); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + public static String getName(int code) { | ||
| 35 | + Stream<Gender> GENDERS = Arrays.stream(values()); | ||
| 36 | + Optional<String> result = GENDERS.filter((gender) -> gender.getCode() == code) | ||
| 37 | + .map(Gender::getName).findFirst(); | ||
| 38 | + return result.orElse(null); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public static List<Gender> getGenders() { | ||
| 42 | + return Arrays.asList(values()); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public String getName() { | ||
| 46 | + return this.name; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public int getCode() { | ||
| 50 | + return this.code; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public String toString() { | ||
| 54 | + return this.name; | ||
| 55 | + } | ||
| 56 | +} | ||
| 0 | \ No newline at end of file | 57 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/type/IEnumType.java
0 → 100644
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/AssertUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/AssertUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ClassUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ClassUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/CurrencyUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/CurrencyUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/DateUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/DateUtils.java | ||
| 1 | +package com.diligrp.cashier.shared.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/HttpUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/HttpUtils.java | ||
| 1 | +package com.diligrp.cashier.shared.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.Constants; | ||
| 4 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 5 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 6 | +import org.slf4j.Logger; | ||
| 7 | +import org.slf4j.LoggerFactory; | ||
| 8 | + | ||
| 9 | +import java.io.BufferedReader; | ||
| 10 | +import java.io.IOException; | ||
| 11 | +import java.nio.charset.StandardCharsets; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * HTTP工具类 | ||
| 15 | + */ | ||
| 16 | +public final class HttpUtils { | ||
| 17 | + | ||
| 18 | + private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class); | ||
| 19 | + | ||
| 20 | + public static String httpBody(HttpServletRequest request) { | ||
| 21 | + StringBuilder payload = new StringBuilder(); | ||
| 22 | + try { | ||
| 23 | + String line; | ||
| 24 | + BufferedReader reader = request.getReader(); | ||
| 25 | + while ((line = reader.readLine()) != null) { | ||
| 26 | + payload.append(line); | ||
| 27 | + } | ||
| 28 | + } catch (IOException iex) { | ||
| 29 | + LOG.error("Failed to extract http body", iex); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + return payload.toString(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public static void sendResponse(HttpServletResponse response, String payload) { | ||
| 36 | + try { | ||
| 37 | + response.setContentType(Constants.CONTENT_TYPE); | ||
| 38 | + byte[] responseBytes = payload.getBytes(StandardCharsets.UTF_8); | ||
| 39 | + response.setContentLength(responseBytes.length); | ||
| 40 | + response.getOutputStream().write(responseBytes); | ||
| 41 | + response.flushBuffer(); | ||
| 42 | + } catch (IOException iex) { | ||
| 43 | + LOG.error("Failed to write data packet back"); | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/JsonUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/JsonUtils.java | ||
| 1 | +package com.diligrp.cashier.shared.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.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.JsonNode; | ||
| 9 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
| 10 | +import com.fasterxml.jackson.databind.SerializationFeature; | ||
| 11 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; | ||
| 12 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; | ||
| 13 | +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; | ||
| 14 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; | ||
| 15 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; | ||
| 16 | +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; | ||
| 17 | +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; | ||
| 18 | + | ||
| 19 | +import java.text.SimpleDateFormat; | ||
| 20 | +import java.time.LocalDate; | ||
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.LocalTime; | ||
| 23 | +import java.time.ZoneOffset; | ||
| 24 | +import java.time.format.DateTimeFormatter; | ||
| 25 | +import java.util.TimeZone; | ||
| 26 | + | ||
| 27 | +public class JsonUtils { | ||
| 28 | + | ||
| 29 | + private static ObjectMapper objectMapper = initObjectMapper(); | ||
| 30 | + | ||
| 31 | + private static ObjectMapper initObjectMapper(){ | ||
| 32 | + Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder(); | ||
| 33 | + initObjectMapperBuilder(jackson2ObjectMapperBuilder); | ||
| 34 | + ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build(); | ||
| 35 | + objectMapper.setSerializerFactory(objectMapper.getSerializerFactory()); | ||
| 36 | + return objectMapper; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public static void initObjectMapperBuilder(Jackson2ObjectMapperBuilder builder) { | ||
| 40 | + //序列化java.util.Date类型 | ||
| 41 | + builder.dateFormat(new SimpleDateFormat(Constants.DATE_TIME_FORMAT)); | ||
| 42 | + builder.timeZone(TimeZone.getTimeZone(ZoneOffset.of("+8"))); | ||
| 43 | + builder.serializationInclusion(JsonInclude.Include.NON_NULL); | ||
| 44 | + builder.featuresToDisable( | ||
| 45 | + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, // Json串的属性无JavaBean字段对应时,避免抛出异常 | ||
| 46 | + DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, // JavaBean中primitive类型的字段无Json属性时,避免抛出异常 | ||
| 47 | + DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, // Json串数字类型属性,赋值JavaBean中Enum字段时,避免抛出异常 | ||
| 48 | + SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, | ||
| 49 | + SerializationFeature.FAIL_ON_EMPTY_BEANS | ||
| 50 | + ); | ||
| 51 | + builder.featuresToEnable( | ||
| 52 | + DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, | ||
| 53 | + DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY | ||
| 54 | + ); | ||
| 55 | + | ||
| 56 | + var dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); | ||
| 57 | + var dateFormatter = DateTimeFormatter.ofPattern(Constants.DATE_FORMAT); | ||
| 58 | + var timeFormatter = DateTimeFormatter.ofPattern(Constants.TIME_FORMAT); | ||
| 59 | + // 添加自定义序列化 | ||
| 60 | + builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); | ||
| 61 | + builder.serializerByType(LocalDate.class, new LocalDateSerializer(dateFormatter)); | ||
| 62 | + builder.serializerByType(LocalTime.class, new LocalTimeSerializer(timeFormatter)); | ||
| 63 | + // 添加自定义反序列化 | ||
| 64 | + builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter)); | ||
| 65 | + builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(dateFormatter)); | ||
| 66 | + builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(timeFormatter)); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public static <T> T fromJsonString(String json, Class<T> type) { | ||
| 70 | + try { | ||
| 71 | + return objectMapper.readValue(json, type); | ||
| 72 | + } catch (JsonProcessingException ex) { | ||
| 73 | + throw new IllegalArgumentException("Deserialize json exception", ex); | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + public static <T> T fromJsonString(String json, TypeReference<T> typeReference){ | ||
| 78 | + try { | ||
| 79 | + return objectMapper.readValue(json, typeReference); | ||
| 80 | + } catch (JsonProcessingException ex) { | ||
| 81 | + throw new IllegalArgumentException("Deserialize json array exception", ex); | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + public static void fromJsonString(Object javaBean, String json) { | ||
| 86 | + try { | ||
| 87 | + objectMapper.readerForUpdating(javaBean).readValue(json); | ||
| 88 | + } catch (JsonProcessingException ex) { | ||
| 89 | + throw new IllegalArgumentException("Deserialize json exception", ex); | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + public static JsonNode fromJsonString(String json) { | ||
| 94 | + try { | ||
| 95 | + return objectMapper.readTree(json); | ||
| 96 | + } catch (JsonProcessingException ex) { | ||
| 97 | + throw new IllegalArgumentException("Deserialize json exception", ex); | ||
| 98 | + } | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + public static <T> T convertValue(Object value, TypeReference<T> typeReference) { | ||
| 102 | + return objectMapper.convertValue(value, typeReference); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + public static <T> String toJsonString(T object) { | ||
| 106 | + try { | ||
| 107 | + return objectMapper.writeValueAsString(object); | ||
| 108 | + } catch (JsonProcessingException ex) { | ||
| 109 | + throw new IllegalArgumentException("Serialize json exception", ex); | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/MathUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/MathUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/NumberUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/NumberUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ObjectUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ObjectUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/RandomUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/RandomUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | \ No newline at end of file | 112 | \ No newline at end of file |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ReflectUtils.java
0 → 100644
| 1 | +++ a/cashier-shared/src/main/java/com/diligrp/cashier/shared/util/ReflectUtils.java | ||
| 1 | +package com.diligrp.cashier.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 | \ No newline at end of file | 93 | \ No newline at end of file |
cashier-trade/build.gradle
0 → 100644
gradle.properties
0 → 100644
gradle/libs.versions.toml
0 → 100644
| 1 | +++ a/gradle/libs.versions.toml | ||
| 1 | +[versions] | ||
| 2 | +appVersion = "1.0.0" | ||
| 3 | +springBootVersion = "3.5.8" | ||
| 4 | +springCloudVersion = "2025.0.0" #springboot4的cloud版本为2025.1.0 | ||
| 5 | +springDependencyVersion = "1.1.7" | ||
| 6 | +springCloudAlibabaVersion = "2025.0.0.0" #目前没有支持springboot4的版本 | ||
| 7 | +mybatisVersion = "3.0.5" #支持springboot4的版本是4.0.0 | ||
| 8 | +mysqlDriverVersion = "8.0.33" | ||
| 9 | +caffeineVersion = "3.2.3" | ||
| 10 | + | ||
| 11 | +[libraries] | ||
| 12 | +#spring-boot-starter = {module = 'org.springframework.boot:spring-boot-starter', version.ref = 'springBootVersion'} | ||
| 13 | +spring-boot-starter-web = {module = 'org.springframework.boot:spring-boot-starter-web', version.ref = 'springBootVersion'} | ||
| 14 | +spring-cloud-nacos-discovery = {module = 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery', version.ref = 'springCloudAlibabaVersion'} | ||
| 15 | +spring-cloud-nacos-config = {module = 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config', version.ref = 'springCloudAlibabaVersion'} | ||
| 16 | +mybatis-starter = {module = 'org.mybatis.spring.boot:mybatis-spring-boot-starter', version.ref = 'mybatisVersion'} | ||
| 17 | +mysql-driver = {module = 'mysql:mysql-connector-java', version.ref = 'mysqlDriverVersion'} | ||
| 18 | +cache-caffeine = {module = 'com.github.ben-manes.caffeine:caffeine', version.ref = 'caffeineVersion'} | ||
| 19 | + | ||
| 20 | +[plugins] | ||
| 21 | +spring-boot = {id = 'org.springframework.boot', version.ref = 'springBootVersion'} | ||
| 22 | +spring-dependency-management = {id = 'io.spring.dependency-management', version.ref = 'springDependencyVersion'} | ||
| 0 | \ No newline at end of file | 23 | \ 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\://mirrors.cloud.tencent.com/gradle/gradle-9.2.1-bin.zip | ||
| 4 | +networkTimeout=10000 | ||
| 5 | +validateDistributionUrl=true | ||
| 6 | +zipStoreBase=GRADLE_USER_HOME | ||
| 7 | +zipStorePath=wrapper/dists |
gradlew
0 → 100755
| 1 | +++ a/gradlew | ||
| 1 | +#!/bin/sh | ||
| 2 | + | ||
| 3 | +# | ||
| 4 | +# Copyright © 2015 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 | +# SPDX-License-Identifier: Apache-2.0 | ||
| 19 | +# | ||
| 20 | + | ||
| 21 | +############################################################################## | ||
| 22 | +# | ||
| 23 | +# Gradle start up script for POSIX generated by Gradle. | ||
| 24 | +# | ||
| 25 | +# Important for running: | ||
| 26 | +# | ||
| 27 | +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | ||
| 28 | +# noncompliant, but you have some other compliant shell such as ksh or | ||
| 29 | +# bash, then to run this script, type that shell name before the whole | ||
| 30 | +# command line, like: | ||
| 31 | +# | ||
| 32 | +# ksh Gradle | ||
| 33 | +# | ||
| 34 | +# Busybox and similar reduced shells will NOT work, because this script | ||
| 35 | +# requires all of these POSIX shell features: | ||
| 36 | +# * functions; | ||
| 37 | +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | ||
| 38 | +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; | ||
| 39 | +# * compound commands having a testable exit status, especially «case»; | ||
| 40 | +# * various built-in commands including «command», «set», and «ulimit». | ||
| 41 | +# | ||
| 42 | +# Important for patching: | ||
| 43 | +# | ||
| 44 | +# (2) This script targets any POSIX shell, so it avoids extensions provided | ||
| 45 | +# by Bash, Ksh, etc; in particular arrays are avoided. | ||
| 46 | +# | ||
| 47 | +# The "traditional" practice of packing multiple parameters into a | ||
| 48 | +# space-separated string is a well documented source of bugs and security | ||
| 49 | +# problems, so this is (mostly) avoided, by progressively accumulating | ||
| 50 | +# options in "$@", and eventually passing that to Java. | ||
| 51 | +# | ||
| 52 | +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | ||
| 53 | +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | ||
| 54 | +# see the in-line comments for details. | ||
| 55 | +# | ||
| 56 | +# There are tweaks for specific operating systems such as AIX, CygWin, | ||
| 57 | +# Darwin, MinGW, and NonStop. | ||
| 58 | +# | ||
| 59 | +# (3) This script is generated from the Groovy template | ||
| 60 | +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||
| 61 | +# within the Gradle project. | ||
| 62 | +# | ||
| 63 | +# You can find Gradle at https://github.com/gradle/gradle/. | ||
| 64 | +# | ||
| 65 | +############################################################################## | ||
| 66 | + | ||
| 67 | +# Attempt to set APP_HOME | ||
| 68 | + | ||
| 69 | +# Resolve links: $0 may be a link | ||
| 70 | +app_path=$0 | ||
| 71 | + | ||
| 72 | +# Need this for daisy-chained symlinks. | ||
| 73 | +while | ||
| 74 | + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path | ||
| 75 | + [ -h "$app_path" ] | ||
| 76 | +do | ||
| 77 | + ls=$( ls -ld "$app_path" ) | ||
| 78 | + link=${ls#*' -> '} | ||
| 79 | + case $link in #( | ||
| 80 | + /*) app_path=$link ;; #( | ||
| 81 | + *) app_path=$APP_HOME$link ;; | ||
| 82 | + esac | ||
| 83 | +done | ||
| 84 | + | ||
| 85 | +# This is normally unused | ||
| 86 | +# shellcheck disable=SC2034 | ||
| 87 | +APP_BASE_NAME=${0##*/} | ||
| 88 | +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||
| 89 | +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | ||
| 90 | + | ||
| 91 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| 92 | +MAX_FD=maximum | ||
| 93 | + | ||
| 94 | +warn () { | ||
| 95 | + echo "$*" | ||
| 96 | +} >&2 | ||
| 97 | + | ||
| 98 | +die () { | ||
| 99 | + echo | ||
| 100 | + echo "$*" | ||
| 101 | + echo | ||
| 102 | + exit 1 | ||
| 103 | +} >&2 | ||
| 104 | + | ||
| 105 | +# OS specific support (must be 'true' or 'false'). | ||
| 106 | +cygwin=false | ||
| 107 | +msys=false | ||
| 108 | +darwin=false | ||
| 109 | +nonstop=false | ||
| 110 | +case "$( uname )" in #( | ||
| 111 | + CYGWIN* ) cygwin=true ;; #( | ||
| 112 | + Darwin* ) darwin=true ;; #( | ||
| 113 | + MSYS* | MINGW* ) msys=true ;; #( | ||
| 114 | + NONSTOP* ) nonstop=true ;; | ||
| 115 | +esac | ||
| 116 | + | ||
| 117 | + | ||
| 118 | + | ||
| 119 | +# Determine the Java command to use to start the JVM. | ||
| 120 | +if [ -n "$JAVA_HOME" ] ; then | ||
| 121 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| 122 | + # IBM's JDK on AIX uses strange locations for the executables | ||
| 123 | + JAVACMD=$JAVA_HOME/jre/sh/java | ||
| 124 | + else | ||
| 125 | + JAVACMD=$JAVA_HOME/bin/java | ||
| 126 | + fi | ||
| 127 | + if [ ! -x "$JAVACMD" ] ; then | ||
| 128 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
| 129 | + | ||
| 130 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 131 | +location of your Java installation." | ||
| 132 | + fi | ||
| 133 | +else | ||
| 134 | + JAVACMD=java | ||
| 135 | + if ! command -v java >/dev/null 2>&1 | ||
| 136 | + then | ||
| 137 | + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 138 | + | ||
| 139 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 140 | +location of your Java installation." | ||
| 141 | + fi | ||
| 142 | +fi | ||
| 143 | + | ||
| 144 | +# Increase the maximum file descriptors if we can. | ||
| 145 | +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | ||
| 146 | + case $MAX_FD in #( | ||
| 147 | + max*) | ||
| 148 | + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. | ||
| 149 | + # shellcheck disable=SC2039,SC3045 | ||
| 150 | + MAX_FD=$( ulimit -H -n ) || | ||
| 151 | + warn "Could not query maximum file descriptor limit" | ||
| 152 | + esac | ||
| 153 | + case $MAX_FD in #( | ||
| 154 | + '' | soft) :;; #( | ||
| 155 | + *) | ||
| 156 | + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. | ||
| 157 | + # shellcheck disable=SC2039,SC3045 | ||
| 158 | + ulimit -n "$MAX_FD" || | ||
| 159 | + warn "Could not set maximum file descriptor limit to $MAX_FD" | ||
| 160 | + esac | ||
| 161 | +fi | ||
| 162 | + | ||
| 163 | +# Collect all arguments for the java command, stacking in reverse order: | ||
| 164 | +# * args from the command line | ||
| 165 | +# * the main class name | ||
| 166 | +# * -classpath | ||
| 167 | +# * -D...appname settings | ||
| 168 | +# * --module-path (only if needed) | ||
| 169 | +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | ||
| 170 | + | ||
| 171 | +# For Cygwin or MSYS, switch paths to Windows format before running java | ||
| 172 | +if "$cygwin" || "$msys" ; then | ||
| 173 | + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||
| 174 | + | ||
| 175 | + JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||
| 176 | + | ||
| 177 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| 178 | + for arg do | ||
| 179 | + if | ||
| 180 | + case $arg in #( | ||
| 181 | + -*) false ;; # don't mess with options #( | ||
| 182 | + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath | ||
| 183 | + [ -e "$t" ] ;; #( | ||
| 184 | + *) false ;; | ||
| 185 | + esac | ||
| 186 | + then | ||
| 187 | + arg=$( cygpath --path --ignore --mixed "$arg" ) | ||
| 188 | + fi | ||
| 189 | + # Roll the args list around exactly as many times as the number of | ||
| 190 | + # args, so each arg winds up back in the position where it started, but | ||
| 191 | + # possibly modified. | ||
| 192 | + # | ||
| 193 | + # NB: a `for` loop captures its iteration list before it begins, so | ||
| 194 | + # changing the positional parameters here affects neither the number of | ||
| 195 | + # iterations, nor the values presented in `arg`. | ||
| 196 | + shift # remove old arg | ||
| 197 | + set -- "$@" "$arg" # push replacement arg | ||
| 198 | + done | ||
| 199 | +fi | ||
| 200 | + | ||
| 201 | + | ||
| 202 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 203 | +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||
| 204 | + | ||
| 205 | +# Collect all arguments for the java command: | ||
| 206 | +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||
| 207 | +# and any embedded shellness will be escaped. | ||
| 208 | +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | ||
| 209 | +# treated as '${Hostname}' itself on the command line. | ||
| 210 | + | ||
| 211 | +set -- \ | ||
| 212 | + "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||
| 213 | + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ | ||
| 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 → 100755
| 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 | +@rem SPDX-License-Identifier: Apache-2.0 | ||
| 17 | +@rem | ||
| 18 | + | ||
| 19 | +@if "%DEBUG%"=="" @echo off | ||
| 20 | +@rem ########################################################################## | ||
| 21 | +@rem | ||
| 22 | +@rem Gradle startup script for Windows | ||
| 23 | +@rem | ||
| 24 | +@rem ########################################################################## | ||
| 25 | + | ||
| 26 | +@rem Set local scope for the variables with windows NT shell | ||
| 27 | +if "%OS%"=="Windows_NT" setlocal | ||
| 28 | + | ||
| 29 | +set DIRNAME=%~dp0 | ||
| 30 | +if "%DIRNAME%"=="" set DIRNAME=. | ||
| 31 | +@rem This is normally unused | ||
| 32 | +set APP_BASE_NAME=%~n0 | ||
| 33 | +set APP_HOME=%DIRNAME% | ||
| 34 | + | ||
| 35 | +@rem Resolve any "." and ".." in APP_HOME to make it shorter. | ||
| 36 | +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | ||
| 37 | + | ||
| 38 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 39 | +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | ||
| 40 | + | ||
| 41 | +@rem Find java.exe | ||
| 42 | +if defined JAVA_HOME goto findJavaFromJavaHome | ||
| 43 | + | ||
| 44 | +set JAVA_EXE=java.exe | ||
| 45 | +%JAVA_EXE% -version >NUL 2>&1 | ||
| 46 | +if %ERRORLEVEL% equ 0 goto execute | ||
| 47 | + | ||
| 48 | +echo. 1>&2 | ||
| 49 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 | ||
| 50 | +echo. 1>&2 | ||
| 51 | +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||
| 52 | +echo location of your Java installation. 1>&2 | ||
| 53 | + | ||
| 54 | +goto fail | ||
| 55 | + | ||
| 56 | +:findJavaFromJavaHome | ||
| 57 | +set JAVA_HOME=%JAVA_HOME:"=% | ||
| 58 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||
| 59 | + | ||
| 60 | +if exist "%JAVA_EXE%" goto execute | ||
| 61 | + | ||
| 62 | +echo. 1>&2 | ||
| 63 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 | ||
| 64 | +echo. 1>&2 | ||
| 65 | +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||
| 66 | +echo location of your Java installation. 1>&2 | ||
| 67 | + | ||
| 68 | +goto fail | ||
| 69 | + | ||
| 70 | +:execute | ||
| 71 | +@rem Setup the command line | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + | ||
| 75 | +@rem Execute Gradle | ||
| 76 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* | ||
| 77 | + | ||
| 78 | +:end | ||
| 79 | +@rem End local scope for the variables with windows NT shell | ||
| 80 | +if %ERRORLEVEL% equ 0 goto mainEnd | ||
| 81 | + | ||
| 82 | +:fail | ||
| 83 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||
| 84 | +rem the _cmd.exe /c_ return code! | ||
| 85 | +set EXIT_CODE=%ERRORLEVEL% | ||
| 86 | +if %EXIT_CODE% equ 0 set EXIT_CODE=1 | ||
| 87 | +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% | ||
| 88 | +exit /b %EXIT_CODE% | ||
| 89 | + | ||
| 90 | +:mainEnd | ||
| 91 | +if "%OS%"=="Windows_NT" endlocal | ||
| 92 | + | ||
| 93 | +:omega |
scripts/dili-cashier.sql
0 → 100644
| 1 | +++ a/scripts/dili-cashier.sql |
settings.gradle
0 → 100644