v1.0.20 #29

Merged
timi merged 1 commits from dev into master 2026-08-21 07:25:32 +00:00
11 changed files with 149 additions and 16 deletions
@@ -42,6 +42,19 @@ public class NotifyQueueConfig {
private String secretId; private String secretId;
private String secretKey; private String secretKey;
private Captcha captcha;
/// 短信验证码
@Data
public static class Captcha {
private String templateId;
private int ttlMinute;
private int argIndex;
}
} }
/// 微信队列配置 /// 微信队列配置
@@ -0,0 +1,30 @@
//package com.imyeyu.api.modules.common.service;
//
//import com.e2ins.ms.notify.entity.Notify;
//import com.e2ins.ms.notify.entity.NotifyDetail;
//
///**
// *
// *
// * @author 夜雨
// * @since 2026-05-14 12:12
// */
//public interface SmsService {
//
// /**
// * 创建短信验证码通知
// *
// * @param detail 通知
// * @return 通知详情 ID
// */
// String captchaCreate(Notify detail);
//
// /**
// * 校验短信验证码
// *
// * @param detailId 通知详情 ID
// * @param captcha 请求验证码
// * @return 通知详情
// */
// NotifyDetail captchaVerify(String detailId, String captcha);
//}
@@ -0,0 +1,82 @@
//package com.imyeyu.api.modules.common.service.implement;
//
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.fasterxml.jackson.databind.node.ArrayNode;
//import com.imyeyu.api.modules.common.config.NotifyQueueConfig;
//import com.imyeyu.api.modules.common.entity.Notify;
//import com.imyeyu.api.modules.common.entity.NotifyDetail;
//import com.imyeyu.api.modules.common.service.NotifyDetailService;
//import com.imyeyu.api.modules.common.service.NotifyService;
//import com.imyeyu.api.modules.common.service.SmsService;
//import com.imyeyu.java.bean.timi.TimiException;
//import com.imyeyu.utils.Regex;
//import com.imyeyu.utils.Text;
//import com.imyeyu.utils.Time;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.stereotype.Service;
//
//import java.util.List;
//
///**
// *
// *
// * @author 夜雨
// * @since 2026-05-14 12:12
// */
//@Slf4j
//@Service
//@RequiredArgsConstructor
//public class SmsServiceImplement implements SmsService {
//
// @Value("${spring.profiles.active}")
// private String env;
//
// private final NotifyQueueConfig config;
// private final ObjectMapper jackson;
//
// private final NotifyService notifyService;
// private final NotifyDetailService notifyDetailService;
//
// @Override
// public String captchaCreate(Notify notify) {
// NotifyQueueConfig.Sms.Captcha captchaConf = config.getSms().getCaptcha();
// TimiException.required(notify.getAppId(), "not found appId");
//
// NotifyDetail detail = notify.getDetailList().getFirst();
//
// TimiException.requiredTrue(Regex.isMatch(Regex.MOBILE_PHONE, detail.getSendTo()), "invalid mobilePhone", "captcha.sms.invalid_mobile_phone");
//
// String code = Text.randomString("0123456789", 6);
// if (!env.equalsIgnoreCase("prod")) {
// code = "123456";
// }
// detail.setMsgType(NotifyDetail.MsgType.SMS);
// detail.setTemplateId(captchaConf.getTemplateId());
// detail.setArgs(jackson.valueToTree(List.of(code, captchaConf.getTtlMinute())));
//
// notifyService.create(notify);
// return notifyDetailService.listByNotifyId(notify.getId()).getFirst().getId();
// }
//
// @Override
// public NotifyDetail captchaVerify(String detailId, String captcha) {
// AppConfig.Sms.Captcha captchaConf = config.getSms().getCaptcha();
//
// NotifyDetail detail = notifyDetailService.get(detailId);
// if (Time.M * captchaConf.getTtlMinute() < Time.now() - detail.getSendAt()) {
// throw new AppException(ResultCode.ARG_EXPIRED).msgKey("captcha.expired");
// }
// if (detail.getArgs() instanceof ArrayNode array) {
// String dbCaptcha = array.get(captchaConf.getArgIndex()).asText();
// if (!dbCaptcha.equalsIgnoreCase(captcha)) {
// throw new AppException(ResultCode.RESULT_BAD).msgKey("captcha.invalid");
// }
// } else {
// throw new AppException(ResultCode.ERROR, "not array args");
// }
// detail.setNotify(notifyService.get(detail.getNotifyId()));
// return detail;
// }
//}
@@ -61,15 +61,15 @@ public class GaoCustomer extends UUIDEntity {
/// 姓名 /// 姓名
private String name; private String name;
@Column("zh_cn_pinyin_full") @Column("name_pinyin_full")
@JsonIgnore @JsonIgnore
private String namePinyinFull; private String namePinyinFull;
@Column("zh_cn_pinyin_initial") @Column("name_pinyin_initial")
@JsonIgnore @JsonIgnore
private String namePinyinInitial; private String namePinyinInitial;
@Column("zh_cn_pinyin_mixed") @Column("name_pinyin_mixed")
@JsonIgnore @JsonIgnore
private String namePinyinMixed; private String namePinyinMixed;
@@ -17,9 +17,12 @@ public class GaoPointLedger extends UUIDEntity {
/// 流水变化类型 /// 流水变化类型
public enum ChangeType { public enum ChangeType {
/// 登记事件积分变化 /// 登记事件发放积分
EARN, EARN,
/// 登记事件消费积分
CONSUME,
/// 删除登记记录撤销积分 /// 删除登记记录撤销积分
REVOKE, REVOKE,
@@ -19,11 +19,11 @@ public interface GaoPointLedgerMapper extends BaseMapper<GaoPointLedger, String>
/// @return 流水 /// @return 流水
GaoPointLedger selectByIdempotencyKey(@Param("idempotencyKey") String idempotencyKey); GaoPointLedger selectByIdempotencyKey(@Param("idempotencyKey") String idempotencyKey);
/// 查询登记记录产生的原始获得积分流水 /// 查询登记记录产生的原始规则积分流水
/// ///
/// @param eventRecordId 登记记录 ID /// @param eventRecordId 登记记录 ID
/// @return 获得积分流水 /// @return 原始规则积分流水
List<GaoPointLedger> selectEarnByEventRecordId(@Param("eventRecordId") String eventRecordId); List<GaoPointLedger> selectRuleChangeByEventRecordId(@Param("eventRecordId") String eventRecordId);
/// 批量查询幂等键对应的流水 /// 批量查询幂等键对应的流水
/// ///
@@ -15,18 +15,18 @@ import com.imyeyu.spring.service.BaseService;
/// @since 2026-08-11 /// @since 2026-08-11
public interface GaoPointLedgerService extends BaseService<GaoPointLedger, String> { public interface GaoPointLedgerService extends BaseService<GaoPointLedger, String> {
/// 写入登记获得积分流水 /// 写入登记规则积分流水
/// ///
/// @param record 登记记录 /// @param record 登记记录
/// @param rule 命中规则 /// @param rule 命中规则
/// @param pointDelta 积分变化 /// @param pointDelta 积分变化
/// @param sourcePayload 来源快照 /// @param sourcePayload 来源快照
/// @return 积分流水 /// @return 积分流水
GaoPointLedger earn(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload); GaoPointLedger applyRule(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload);
/// 写入删除登记记录的撤销流水 /// 写入删除登记记录的撤销流水
/// ///
/// @param originLedger 原始获得积分流水 /// @param originLedger 原始规则积分流水
/// @param record 登记记录 /// @param record 登记记录
/// @return 撤销流水 /// @return 撤销流水
GaoPointLedger revoke(GaoPointLedger originLedger, GaoEventRecord record); GaoPointLedger revoke(GaoPointLedger originLedger, GaoEventRecord record);
@@ -71,12 +71,13 @@ public class GaoPointLedgerServiceImplement extends AbstractEntityService<GaoPoi
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public GaoPointLedger earn(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload) { public GaoPointLedger applyRule(GaoEventRecord record, GaoPointRule rule, long pointDelta, JsonNode sourcePayload) {
TimiException.required(record, "not found event record"); TimiException.required(record, "not found event record");
TimiException.required(rule, "not found point rule"); TimiException.required(rule, "not found point rule");
TimiException.requiredTrue(pointDelta != 0, "pointDelta invalid"); TimiException.requiredTrue(pointDelta != 0, "pointDelta invalid");
String idempotencyKey = "GAO_EVENT_RECORD:%s:RULE:%s:EARN".formatted(record.getId(), rule.getId()); String idempotencyKey = "GAO_EVENT_RECORD:%s:RULE:%s:EARN".formatted(record.getId(), rule.getId());
return apply(record.getStoreId(), record.getCustomerId(), GaoPointLedger.ChangeType.EARN, pointDelta, rule.getId(), record.getEventId(), record.getId(), sourcePayload, idempotencyKey, rule.getRemark(), record.getOperatorUserId(), record.getRegisteredAt()); GaoPointLedger.ChangeType changeType = pointDelta < 0 ? GaoPointLedger.ChangeType.CONSUME : GaoPointLedger.ChangeType.EARN;
return apply(record.getStoreId(), record.getCustomerId(), changeType, pointDelta, rule.getId(), record.getEventId(), record.getId(), sourcePayload, idempotencyKey, rule.getRemark(), record.getOperatorUserId(), record.getRegisteredAt());
} }
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -73,7 +73,7 @@ public class GaoPointTriggerServiceImplement implements GaoPointTriggerService {
} }
long pointDelta = resolution.pointDelta(); long pointDelta = resolution.pointDelta();
if (pointDelta != 0) { if (pointDelta != 0) {
ledgerService.earn(record, rule, pointDelta, resolution.sourcePayload()); ledgerService.applyRule(record, rule, pointDelta, resolution.sourcePayload());
pointDeltaTotal = Math.addExact(pointDeltaTotal, pointDelta); pointDeltaTotal = Math.addExact(pointDeltaTotal, pointDelta);
} }
if (Boolean.TRUE.equals(rule.getExclusive())) { if (Boolean.TRUE.equals(rule.getExclusive())) {
@@ -88,7 +88,7 @@ public class GaoPointTriggerServiceImplement implements GaoPointTriggerService {
@Override @Override
public void revokeByEventRecord(GaoEventRecord record) { public void revokeByEventRecord(GaoEventRecord record) {
TimiException.required(record, "not found event record"); TimiException.required(record, "not found event record");
List<GaoPointLedger> originLedgerList = ledgerMapper.selectEarnByEventRecordId(record.getId()); List<GaoPointLedger> originLedgerList = ledgerMapper.selectRuleChangeByEventRecordId(record.getId());
if (originLedgerList.isEmpty()) { if (originLedgerList.isEmpty()) {
return; return;
} }
@@ -0,0 +1,4 @@
UPDATE `gao_point_ledger`
SET `change_type` = 'CONSUME'
WHERE `change_type` = 'EARN'
AND `point_delta` < 0;
@@ -9,11 +9,11 @@
LIMIT 1 LIMIT 1
</select> </select>
<select id="selectEarnByEventRecordId" resultType="com.imyeyu.api.modules.gao.entity.GaoPointLedger"> <select id="selectRuleChangeByEventRecordId" resultType="com.imyeyu.api.modules.gao.entity.GaoPointLedger">
SELECT * SELECT *
FROM `gao_point_ledger` FROM `gao_point_ledger`
WHERE `event_record_id` = #{eventRecordId} WHERE `event_record_id` = #{eventRecordId}
AND `change_type` = 'EARN' AND `change_type` IN ('EARN', 'CONSUME')
AND `deleted_at` IS NULL AND `deleted_at` IS NULL
ORDER BY `created_at` ASC, `id` ASC ORDER BY `created_at` ASC, `id` ASC
</select> </select>