Compare commits

...

69 Commits

Author SHA1 Message Date
f124a08377 Merge branch 'main' into dev 2025-11-13 10:23:41 +08:00
6ca0af397b 日志级别debug 改成 info 2025-11-13 09:50:17 +08:00
ed10a03442 日志级别debug 改成 info 2025-11-13 09:47:40 +08:00
6c479e744a 增加确认收货通知 逻辑 2025-11-13 01:20:30 +08:00
ad7f89cb42 新增redisson锁和redisson锁切面,保证分布式锁,分布式id 2025-11-12 18:27:23 +08:00
b04f2d095d 修改 diy.html 获取 token 测试代码 2025-11-12 14:31:36 +08:00
b06826593a 分布式锁公共方法 2025-11-12 10:46:39 +08:00
a43df002dc 分布式锁公共方法 2025-11-12 10:44:28 +08:00
2ecfce1f2f 修改配置 2025-11-11 23:25:11 +08:00
e9c42bdb99 Merge remote-tracking branch 'origin/main' 2025-11-11 21:05:20 +08:00
af9cce0db1 店铺状态 2025-11-11 21:05:06 +08:00
645ad45d08 思迅单机改为分布式商品导入 2025-11-11 19:07:06 +08:00
41dbafffab 新增默认的库存 2025-11-11 08:50:38 +08:00
5e45474807 商品映射新增商品货号查询 2025-11-10 18:08:34 +08:00
c3414925a0 商品映射新增商品货号查询 2025-11-10 17:18:34 +08:00
615d478b4c 新增数据库存储文件内容,新增配置字段 2025-11-10 16:22:01 +08:00
cb74d80aab 砍价逻辑状态定时任务补充 2025-11-09 01:24:52 +08:00
0152518315 更新逻辑修改 2025-11-08 10:29:11 +08:00
c8a85d5119 流水逻辑修改 2025-11-08 10:28:58 +08:00
31160d4806 新增事务方案修复掉线问题导致的重复消费 2025-11-08 10:28:49 +08:00
d59096f6d3 活动同步问题修复 2025-11-08 10:28:38 +08:00
10fa6fd739 活动同步问题修复 2025-11-08 10:28:30 +08:00
accd09409d 思迅同步销售流水和支付流水,调价时间查询新增 2025-11-08 10:28:19 +08:00
eaf1a87db8 思迅同步销售流水和支付流水 2025-11-08 10:28:04 +08:00
d4a7c4b3fb 思迅设置用户名称 2025-11-08 10:27:09 +08:00
62eb843629 新增库存扣减中间表的消费,保证库存的扣减一致性 2025-11-08 10:26:51 +08:00
ef36db0b44 服务器下载路径修改 2025-11-08 10:26:07 +08:00
2d612ecc7a 商品分类逻辑,去除父类 2025-11-08 10:25:54 +08:00
2a2ec2f9b3 活动客户端代码提交 2025-11-08 10:25:28 +08:00
c587ad5532 活动客户端代码提交 2025-11-08 10:25:04 +08:00
d2cdc097f8 客户端代码提交 2025-11-08 10:24:19 +08:00
ee31d1f75d 防篡改问题修复 2025-11-08 10:23:44 +08:00
80607c8642 思迅查询去除淘汰商品 2025-11-08 10:23:29 +08:00
ce974276a8 思迅同步活动问题修复 2025-11-08 10:22:57 +08:00
da259b15e9 思迅同步品牌问题修复 2025-11-08 10:21:07 +08:00
d1ef71669e 更新逻辑修改 2025-11-08 10:14:45 +08:00
44794636f7 同步商品命名修改恢复 2025-11-08 10:12:18 +08:00
19756ee7e3 砍价逻辑修改 2025-11-08 00:46:34 +08:00
2e7612ac93 解决商品不存在时的扣减出错问题 2025-11-07 17:42:47 +08:00
38a1ce0c66 解决下载密钥解析问题 2025-11-07 17:21:59 +08:00
415bbfd4ba 砍价字段修改,补充逻辑 2025-11-07 16:57:46 +08:00
cfb8714410 打印日志,检查下载包下载失败问题 2025-11-07 16:51:28 +08:00
070c1fc625 重构shop_store_activity_base表,解决版本不兼容问题 2025-11-07 16:51:16 +08:00
eeb7f05706 退单针对商品不存在的问题处理 2025-11-07 14:37:02 +08:00
2661277bd2 砍价字段修改 2025-11-07 12:42:01 +08:00
db33dd255e 时间字段命名修复 2025-11-07 09:00:28 +08:00
d2289f2d12 砍价字段修改 2025-11-07 00:35:05 +08:00
507b65838c fix 预约订单时间槽,增加 开业筹备中的店铺逻辑 2025-11-06 22:52:59 +08:00
bb0eb917c1 退款总额取值修复 2025-11-06 18:22:47 +08:00
f3cf329f19 Merge branch 'main' of http://git.gpxscs.cn/backend/java-mall 2025-11-06 18:02:05 +08:00
3287be123e createTime和updateTime时间已有删除重复设置值 2025-11-06 18:01:52 +08:00
57ccf4f599 fix 预约订单时间槽 2025-11-06 18:01:15 +08:00
3c19ee7064 手动新增商品时新增item条码 2025-11-06 17:31:47 +08:00
3ee9eaee8d 自取更新时间 2025-11-06 17:31:34 +08:00
30bf1446d4 Merge remote-tracking branch 'origin/main' 2025-11-06 16:58:59 +08:00
45a2f7a07d 修复 实体类的 错误, 优化打包指令 2025-11-06 16:58:52 +08:00
1aea5d0281 金额取整数 2025-11-06 16:49:09 +08:00
38b674e05a 砍价活动 时间槽 修正新需求。 增加必要的字段 2025-11-06 12:38:06 +08:00
0c15b2c034 退款新增总额 2025-11-06 09:36:52 +08:00
4814ca202d 退款新增总额 2025-11-06 09:36:18 +08:00
2ab4495920 时间槽调整 2025-11-06 00:57:46 +08:00
ea0afa5bdb 白名单调整 2025-11-05 17:24:42 +08:00
3a4a8d7772 打印同步规格日志 2025-11-05 16:36:13 +08:00
12fbd15a66 Merge remote-tracking branch 'origin/main' 2025-11-05 15:13:03 +08:00
d711a70e34 项目打包配置修复 2025-11-05 15:12:53 +08:00
b9d4d8744f 更新时不再自动匹配图库,以提升服务器性能 2025-11-05 09:14:30 +08:00
5c6bf1bb3f 服务器网卡配置 2025-11-05 02:41:47 +08:00
ef4db7a0bf 活动商品字段改变逻辑修改 2025-11-04 18:12:45 +08:00
fa80c2fca3 店铺营业状态增加 开业活动筹备中 字段 2025-11-04 17:50:37 +08:00
46 changed files with 2892 additions and 1645 deletions

View File

@ -99,7 +99,7 @@ logging:
suisung:
mall:
account:
mapper: debug
mapper: info
sun:
mail: error
baomidou: error

View File

@ -127,4 +127,26 @@ public class CommonConstant {
// 预约下单从当前时间延迟的最小分钟数单位分钟不能低于35分钟
public final static Integer MIN_DELAY_MINUTES_FOR_BOOKING_ORDER = 50;
// 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外2-停业中3-开业或活动筹备中
// 1-开业营业中且在营业时间内
public final static Integer Store_Biz_State_Opening = 1;
// 12-开业打烊中但在营业时间外
public final static Integer Store_Biz_State_Opening2 = 12;
//2-停业中
public final static Integer Store_Biz_State_Closed = 2;
//3-开业(活动)筹备中
public final static Integer Store_Biz_State_PreActivity = 3;
//用户砍价订单状态1-砍价已完成下单
public static final Integer CutPrice_Order_State_Finished = 1;
//2-砍价未下单已取消
public static final Integer CutPrice_Order_State_Canceled = 2;
//3-砍价助力进行中
public static final Integer CutPrice_Order_State_ING = 3;
//4-砍价过期失效
public static final Integer CutPrice_Order_State_Expired = 4;
//6-砍价助力已完成待下单
public static final Integer CutPrice_Order_State_CutFinished = 6;
}

View File

@ -56,8 +56,17 @@ public class ShopActivityCutprice implements Serializable {
@ApiModelProperty(value = "砍价人数")
private Integer ac_num;
@ApiModelProperty(value = "砍价过期时间戳")
private Long expired_at;
@ApiModelProperty(value = "用户砍价订单状态1-砍价已完成下单2-砍价未下单已取消3-砍价助力进行中4-砍价过期失效6-砍价助力已完成待下单;")
private Integer state;
@Version
@ApiModelProperty(value = "乐观锁")
private Integer version;
@ApiModelProperty(value = "更新时间")
private Date updated_at;
}

View File

@ -0,0 +1,42 @@
package com.suisung.mall.common.modules.lakala;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 拉卡拉确认收货通知数据日志
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("lkl_receive_notify_log")
@ApiModel(value = "拉卡拉确认收货通知数据日志", description = "拉卡拉确认收货通知数据日志")
public class LklReceiveNotifyLog {
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty(value = "自增 Id", example = "1")
private Long id;
@ApiModelProperty(value = "订单Id")
private String orderId = "";
@ApiModelProperty(value = "通知 JSON 响应数据")
private String respJson;
@ApiModelProperty(value = "使用状态1-被使用过2-未被使用;")
private Integer status = 2;
@ApiModelProperty(value = "创建时间")
private Date createdAt;
@ApiModelProperty(value = "更新时间")
private Date updatedAt;
}

View File

@ -1,6 +1,9 @@
package com.suisung.mall.common.modules.store;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -119,7 +122,6 @@ public class ShopStoreActivityBase implements Serializable {
@TableField(updateStrategy = NOT_EMPTY)
private Integer activity_share_num;
@Version
@ApiModelProperty(value = "乐观锁")
private Integer version;
@ -127,4 +129,10 @@ public class ShopStoreActivityBase implements Serializable {
@TableField(updateStrategy = NOT_EMPTY)
private String flow_no;
@ApiModelProperty(value = "在活动时间范围内,用户砍价有效期(小时)")
private Integer cut_hour;
@ApiModelProperty(value = "参与活动商品的总数量(个)")
private Integer product_count;
}

View File

@ -80,9 +80,12 @@ public class ShopStoreBase implements Serializable {
@ApiModelProperty(value = "店铃声开关1-开启2-关闭;")
private Integer ringtone_is_enable;
@ApiModelProperty(value = "店铺营业状态1-营业中2-已打烊")
@ApiModelProperty(value = "店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外2-停业中3-开业(或活动)筹备中")
private Integer store_biz_state;
@ApiModelProperty(value = "开业(活动)筹备日期 yyyy-MM-dd")
private Date store_biz_opening_date;
@ApiModelProperty(value = "上级店铺编号:创建店铺决定,所属分销商-不可更改! 佣金公平性考虑")
private Integer shop_parent_id;

View File

@ -2,6 +2,7 @@ package com.suisung.mall.common.pojo.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import java.math.BigDecimal;
@ -29,10 +30,11 @@ import java.math.RoundingMode;
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Slf4j
public class LklSeparateWithTotalAmountDTO {
// 常量定义
private static final BigDecimal MCH_RATIO_THRESHOLD = new BigDecimal("0.2");
private static final BigDecimal MCH_RATIO_THRESHOLD = BigDecimal.valueOf(0.2);
// 基础金额属性
private Integer totalSeparateAmount; // 分账总金额()
@ -41,7 +43,7 @@ public class LklSeparateWithTotalAmountDTO {
private Integer shippingFee; // 配送费()
// 分账比例属性
private BigDecimal lklRatio; // 拉卡拉分账比例( 0.0025=0.25%)
private BigDecimal lklRatio; // 拉卡拉分账比例( 0.0025=0.025%)
private BigDecimal mchRatio; // 商户分账比例( 0.96=96%)
private BigDecimal platRatio; // 平台分账比例( 0.01=1%)
private BigDecimal agent1stRatio; // 一级代理商分账比例( 0.01=1%)
@ -53,6 +55,7 @@ public class LklSeparateWithTotalAmountDTO {
private Integer platAmount; // 平台分账金额()
private Integer agent1stAmount; // 一级代理商分账金额()
private Integer agent2ndAmount; // 二级代理商分账金额()
private String errMsg; // 错误信息字段
/**
* 测试方法
@ -62,26 +65,75 @@ public class LklSeparateWithTotalAmountDTO {
// 测试用例1: 所有参与方都参与分账符合比例要求
System.out.println("=== 测试用例1: 所有参与方都参与分账 ===");
LklSeparateWithTotalAmountDTO dto1 = new LklSeparateWithTotalAmountDTO();
dto1.setTotalSeparateAmount(1696); // 总金额100元(10000)
dto1.setTotalSeparateAmount(800); // 总金额16.96元(1696)
dto1.setShippingFee(500);
// dto1.setRefCanSeparateAmount(1496);
dto1.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉分账比例0.25%
dto1.setMchRatio(new BigDecimal("0.96")); // 商家分账比例94.75%
dto1.setPlatRatio(new BigDecimal("0.04")); // 平台分账比例1%
// dto1.setAgent2ndRatio(new BigDecimal("0.04")); // 二级代理商分账比例4%
// dto1.setAgent1stRatio(new BigDecimal("0.01")); // 一级代理商分账比例1%
dto1.setLklRatio(BigDecimal.valueOf(0.0025)); // 拉卡拉分账比例0.25%
dto1.setMchRatio(BigDecimal.valueOf(0.96)); // 商家分账比例94.75%
dto1.setPlatRatio(BigDecimal.valueOf(0.04)); // 平台分账比例1%
// dto1.setAgent2ndRatio(BigDecimal.valueOf(0.04)); // 二级代理商分账比例4%
// dto1.setAgent1stRatio(BigDecimal.valueOf(0.01)); // 一级代理商分账比例1%
Pair<Boolean, LklSeparateWithTotalAmountDTO> result = dto1.calculateSeparateAmount();
if (result.getFirst()) {
SeparateResult result = dto1.calculateSeparateAmount();
if (result.getIsSuccess()) {
System.out.println("分账计算成功:");
System.out.println(result.getSecond());
System.out.println(result.getData());
System.out.println("JSON格式输出:");
System.out.println(result.getSecond().toJSON());
System.out.println(result.getData().toJSON());
} else {
System.out.println("分账计算失败");
if (result.getSecond() != null) {
if (result.getErrMsg() != null) {
System.out.println("部分结果:");
System.out.println(result.getSecond());
System.out.println(result.getErrMsg());
// 输出错误信息
if (result.getErrMsg() != null && !result.getErrMsg().isEmpty()) {
System.out.println("错误信息: " + result.getErrMsg());
}
}
}
// 测试用例2: 商家分账比例过低的情况
System.out.println("\n=== 测试用例2: 商家分账比例过低 ===");
LklSeparateWithTotalAmountDTO dto2 = new LklSeparateWithTotalAmountDTO();
dto2.setTotalSeparateAmount(10000); // 总金额100元(10000分)
dto2.setLklRatio(BigDecimal.valueOf(0.0025)); // 拉卡拉分账比例0.25%
dto2.setMchRatio(BigDecimal.valueOf(0.1)); // 商家分账比例过低(10%)
dto2.setPlatRatio(BigDecimal.valueOf(0.04)); // 平台分账比例4%
SeparateResult result2 = dto2.calculateSeparateAmount();
if (result2.getIsSuccess()) {
System.out.println("分账计算成功:");
System.out.println(result2.getData());
} else {
System.out.println("分账计算失败");
if (result2.getData() != null) {
System.out.println("部分结果:");
System.out.println(result2.getData());
// 输出错误信息
if (result2.getErrMsg() != null && !result2.getErrMsg().isEmpty()) {
System.out.println("错误信息: " + result2.getErrMsg());
}
}
}
// 测试用例3: 使用calculateSeparateAmountWithResult方法获取SeparateResult结果
System.out.println("\n=== 测试用例3: 使用calculateSeparateAmountWithResult方法获取SeparateResult结果 ===");
LklSeparateWithTotalAmountDTO dto3 = new LklSeparateWithTotalAmountDTO();
dto3.setTotalSeparateAmount(10000); // 总金额100元(10000分)
dto3.setLklRatio(BigDecimal.valueOf(0.0025)); // 拉卡拉分账比例0.25%
dto3.setMchRatio(BigDecimal.valueOf(0.8)); // 商家分账比例80%
dto3.setPlatRatio(BigDecimal.valueOf(0.04)); // 平台分账比例4%
LklSeparateWithTotalAmountDTO.SeparateResult separateResult = dto3.calculateSeparateAmountWithResult();
if (separateResult.getIsSuccess()) {
System.out.println("分账计算成功:");
System.out.println(separateResult.getData());
} else {
System.out.println("分账计算失败: " + separateResult.getErrMsg());
if (separateResult.getData() != null) {
System.out.println("部分结果:");
System.out.println(separateResult.getData());
}
}
}
@ -89,63 +141,124 @@ public class LklSeparateWithTotalAmountDTO {
/**
* 执行分账计算逻辑
*
* @return Pair<Boolean, LklSeparateWithTotalAmountDTO> Boolean表示是否成功LklSeparateWithTotalAmountDTO为分账结果
* @return SeparateResult 包含成功状态分账结果和错误信息的包装类
*/
public Pair<Boolean, LklSeparateWithTotalAmountDTO> calculateSeparateAmount() {
public SeparateResult calculateSeparateAmount() {
try {
// 清空之前的错误信息
this.errMsg = null;
// 参数校验
validateInputs();
Pair<Boolean, String> validateResult = validateInputs();
if (!validateResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + validateResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
// 1. 计算拉卡拉分账金额和可分账金额
calculateLklAmountAndCanSeparateAmount();
Pair<Boolean, String> lklResult = calculateLklAmountAndCanSeparateAmount();
if (!lklResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + lklResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
// 2. 确定实际可分账金额
int actualCanSeparateAmount = determineActualCanSeparateAmount();
Pair<Boolean, String> canSeparateResult = determineActualCanSeparateAmount();
if (!canSeparateResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + canSeparateResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
int actualCanSeparateAmount = Integer.parseInt(canSeparateResult.getSecond());
// 3. 计算各参与方分账比例
calculateDefaultRatios();
Pair<Boolean, String> ratioResult = calculateDefaultRatios();
if (!ratioResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + ratioResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
// 4. 根据优先级顺序计算各参与方分账金额
calculateAmountsInPriorityOrder(actualCanSeparateAmount);
Pair<Boolean, String> amountResult = calculateAmountsInPriorityOrder(actualCanSeparateAmount);
if (!amountResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + amountResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
// 5. 校验分账金额总和不能超过总金额
validateSeparateAmountTotal();
Pair<Boolean, String> totalResult = validateSeparateAmountTotal();
if (!totalResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + totalResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
// 6. 计算商家实际分账比例
calculateActualMchRatio();
Pair<Boolean, String> mchResult = calculateActualMchRatio();
if (!mchResult.getFirst()) {
String errorMsg = "分账计算参数异常: " + mchResult.getSecond();
log.error(errorMsg);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
return Pair.of(true, this);
return SeparateResult.success(this);
} catch (IllegalArgumentException e) {
// 参数校验异常返回false和当前对象
System.err.println("分账计算参数异常: " + e.getMessage());
return Pair.of(false, this);
String errorMsg = "分账计算参数异常: " + e.getMessage();
log.error(errorMsg, e);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
} catch (Exception e) {
// 其他异常返回false和null
System.err.println("分账计算异常: " + e.getMessage());
return Pair.of(false, null);
String errorMsg = "分账计算异常: " + e.getMessage();
log.error(errorMsg, e);
this.errMsg = errorMsg;
return SeparateResult.failure(errorMsg);
}
}
/**
* 校验必要参数
*
* @throws IllegalArgumentException 当参数不合法时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void validateInputs() {
private Pair<Boolean, String> validateInputs() {
// 校验totalSeparateAmount必须为有效值且大于0
if (totalSeparateAmount == null || totalSeparateAmount <= 0) {
throw new IllegalArgumentException("分账计算缺少必要参数或参数不合法");
log.error("分账计算:总分账金额小于等于0");
return Pair.of(false, "分账计算:总分账金额小于等于0");
}
// 校验必要参数
if (lklRatio == null || mchRatio == null) {
throw new IllegalArgumentException("分账计算缺少必要参数");
if (lklRatio == null || lklRatio.compareTo(BigDecimal.ZERO) <= 0) {
log.error("分账计算:拉卡拉分账比例小于等于0");
return Pair.of(false, "分账计算:拉卡拉分账比例小于等于0");
}
if (mchRatio == null || mchRatio.compareTo(BigDecimal.ZERO) <= 0) {
log.error("分账计算:商户分账比例小于等于0");
return Pair.of(false, "分账计算:商户分账比例小于等于0");
}
// 校验shippingFee不能大于等于totalSeparateAmount
if (shippingFee != null && shippingFee >= totalSeparateAmount) {
throw new IllegalArgumentException("配送费不能大于等于总金额");
log.error("分账计算:分账总金额低于平台内部运费");
return Pair.of(false, "分账计算:分账总金额低于平台内部运费");
}
return Pair.of(true, "");
}
/**
@ -153,19 +266,20 @@ public class LklSeparateWithTotalAmountDTO {
* 拉卡拉分账金额 = 总金额 * 拉卡拉分账比例四舍五入
* 可分账金额 = 总金额 - 拉卡拉分账金额
*
* @throws IllegalArgumentException 当计算出的分账金额为负数时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void calculateLklAmountAndCanSeparateAmount() {
private Pair<Boolean, String> calculateLklAmountAndCanSeparateAmount() {
// 拉卡拉分账金额 = 总金额 * 拉卡拉分账比例四舍五入
lklAmount = lklRatio.multiply(new BigDecimal(totalSeparateAmount))
lklAmount = lklRatio.multiply(BigDecimal.valueOf(totalSeparateAmount))
.setScale(0, RoundingMode.HALF_UP)
.intValue();
// 校验拉卡拉分账金额不能为负数
if (lklAmount < 0) {
// 记录详细日志
System.err.println("拉卡拉分账金额计算结果为负数: " + lklAmount);
throw new IllegalArgumentException("拉卡拉分账金额计算异常");
String errorMsg = "拉卡拉分账金额计算结果为负数: " + lklAmount;
log.error(errorMsg);
return Pair.of(false, "拉卡拉分账金额计算异常");
}
// 可分账金额 = 总金额 - 拉卡拉分账金额
@ -174,9 +288,12 @@ public class LklSeparateWithTotalAmountDTO {
// 校验可分账金额不能为负数
if (canSeparateAmount < 0) {
// 记录详细日志
System.err.println("可分账金额计算结果为负数: " + canSeparateAmount);
throw new IllegalArgumentException("可分账金额计算异常");
String errorMsg = "可分账金额计算结果为负数: " + canSeparateAmount;
log.error(errorMsg);
return Pair.of(false, "可分账金额计算异常");
}
return Pair.of(true, "");
}
/**
@ -184,10 +301,9 @@ public class LklSeparateWithTotalAmountDTO {
* 实际可分账金额A: 如果refCanSeparateAmount有效则使用否则使用canSeparateAmount
* 实际可分账金额B = 实际可分账金额A - 配送费如果配送费有效
*
* @return 实际可分账金额
* @throws IllegalArgumentException 当实际可分账金额为负数时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private int determineActualCanSeparateAmount() {
private Pair<Boolean, String> determineActualCanSeparateAmount() {
// 实际可分账金额A: 如果refCanSeparateAmount有效则使用否则使用canSeparateAmount
int actualCanSeparateAmountA = (refCanSeparateAmount != null && refCanSeparateAmount > 0)
? refCanSeparateAmount : canSeparateAmount;
@ -201,22 +317,26 @@ public class LklSeparateWithTotalAmountDTO {
// 校验实际可分账金额不能为负数
if (actualCanSeparateAmountB < 0) {
// 记录详细日志
System.err.println("实际可分账金额计算结果为负数: " + actualCanSeparateAmountB);
throw new IllegalArgumentException("实际可分账金额计算异常");
String errorMsg = "实际可分账金额计算结果为负数: " + actualCanSeparateAmountB;
log.error(errorMsg);
return Pair.of(false, "实际可分账金额计算异常");
}
return actualCanSeparateAmountB;
return Pair.of(true, String.valueOf(actualCanSeparateAmountB));
}
/**
* 计算各参与方分账比例
* 如果平台比例无效设置默认值0.01
*
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void calculateDefaultRatios() {
private Pair<Boolean, String> calculateDefaultRatios() {
// 如果平台比例无效设置默认值0.01
if (platRatio == null || platRatio.compareTo(BigDecimal.ZERO) <= 0) {
platRatio = new BigDecimal("0.01");
}
return Pair.of(true, "");
}
/**
@ -225,41 +345,35 @@ public class LklSeparateWithTotalAmountDTO {
* 商家获得所有剩余金额
*
* @param actualCanSeparateAmount 实际可分账金额
* @throws IllegalArgumentException 当计算出的分账金额为负数或分账金额超过可用金额时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void calculateAmountsInPriorityOrder(int actualCanSeparateAmount) {
private Pair<Boolean, String> calculateAmountsInPriorityOrder(int actualCanSeparateAmount) {
// 重置所有分账金额
platAmount = 0;
agent1stAmount = 0;
agent2ndAmount = 0;
mchAmount = 0;
// 1. 计算拉卡拉分账金额拉卡拉一定参与分账
lklAmount = lklRatio.multiply(new BigDecimal(totalSeparateAmount))
.setScale(0, RoundingMode.HALF_UP)
.intValue();
// 校验拉卡拉分账金额不能为负数
if (lklAmount < 0) {
throw new IllegalArgumentException("拉卡拉分账金额计算异常");
}
// 2. 校验实际可分账金额不能小于0
if (actualCanSeparateAmount < 0) {
throw new IllegalArgumentException("实际可分账金额不能为负数");
String errorMsg = "实际可分账金额不能为负数";
log.error(errorMsg);
return Pair.of(false, errorMsg);
}
// 3. 初始化剩余金额为实际可分账金额
int remainingAmount = actualCanSeparateAmount;
// 4. 计算平台分账金额平台一定参与分账
platAmount = platRatio.multiply(new BigDecimal(totalSeparateAmount))
platAmount = platRatio.multiply(BigDecimal.valueOf(totalSeparateAmount))
.setScale(0, RoundingMode.HALF_UP)
.intValue();
// 校验平台分账金额不能为负数
if (platAmount < 0) {
throw new IllegalArgumentException("平台分账金额计算异常");
String errorMsg = "平台分账金额计算异常";
log.error(errorMsg);
return Pair.of(false, errorMsg);
}
// 确保不超过剩余金额
@ -278,13 +392,15 @@ public class LklSeparateWithTotalAmountDTO {
// 优先级平台 -> 二级代理商 -> 一级代理商
if (isAgent2ndParticipate) {
// 计算二级代理商分账金额
agent2ndAmount = agent2ndRatio.multiply(new BigDecimal(totalSeparateAmount))
agent2ndAmount = agent2ndRatio.multiply(BigDecimal.valueOf(totalSeparateAmount))
.setScale(0, RoundingMode.HALF_UP)
.intValue();
// 校验二级代理商分账金额不能为负数
if (agent2ndAmount < 0) {
throw new IllegalArgumentException("二级代理商分账金额计算异常");
String errorMsg = "二级代理商分账金额计算异常";
log.error(errorMsg);
return Pair.of(false, errorMsg);
}
// 确保不超过剩余金额
@ -296,13 +412,15 @@ public class LklSeparateWithTotalAmountDTO {
if (isAgent1stParticipate) {
// 计算一级代理商分账金额
agent1stAmount = agent1stRatio.multiply(new BigDecimal(totalSeparateAmount))
agent1stAmount = agent1stRatio.multiply(BigDecimal.valueOf(totalSeparateAmount))
.setScale(0, RoundingMode.HALF_UP)
.intValue();
// 校验一级代理商分账金额不能为负数
if (agent1stAmount < 0) {
throw new IllegalArgumentException("一级代理商分账金额计算异常");
String errorMsg = "一级代理商分账金额计算异常";
log.error(errorMsg);
return Pair.of(false, errorMsg);
}
// 确保不超过剩余金额
@ -317,37 +435,44 @@ public class LklSeparateWithTotalAmountDTO {
// 校验商家分账金额不能为负数
if (mchAmount < 0) {
throw new IllegalArgumentException("商家分账金额计算异常");
String errorMsg = "商家分账金额计算异常";
log.error(errorMsg);
return Pair.of(false, errorMsg);
}
return Pair.of(true, "");
}
/**
* 计算商家实际分账比例
*
* @throws IllegalArgumentException 当商家实际分账比例低于阈值时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void calculateActualMchRatio() {
private Pair<Boolean, String> calculateActualMchRatio() {
if (totalSeparateAmount != null && totalSeparateAmount > 0 && mchAmount != null) {
// 计算实际比例保留6位小数
mchRatio = new BigDecimal(mchAmount)
.divide(new BigDecimal(totalSeparateAmount), 6, RoundingMode.HALF_UP);
mchRatio = BigDecimal.valueOf(mchAmount)
.divide(BigDecimal.valueOf(totalSeparateAmount), 6, RoundingMode.HALF_UP);
// 如果计算出的实际比例低于阈值打印日志并抛出异常
// 如果计算出的实际比例低于阈值打印日志并返回错误
if (mchRatio.compareTo(MCH_RATIO_THRESHOLD) < 0) {
System.err.println("警告: 商家实际分账比例低于阈值,当前比例: " + mchRatio + ",阈值: " + MCH_RATIO_THRESHOLD);
throw new IllegalArgumentException("商家分账低于阈值");
String errorMsg = "警告: 商家实际分账比例低于阈值,当前比例: " + mchRatio + ",阈值: " + MCH_RATIO_THRESHOLD;
log.warn(errorMsg);
return Pair.of(false, "商家分账低于阈值");
}
}
return Pair.of(true, "");
}
/**
* 校验分账金额总和不能超过总金额
*
* @throws IllegalArgumentException 当分账金额总和超过总金额时抛出异常
* @return Pair<Boolean, String> Boolean表示是否成功String为错误信息
*/
private void validateSeparateAmountTotal() {
int totalAmount = 0;
private Pair<Boolean, String> validateSeparateAmountTotal() {
long totalAmount = 0;
totalAmount += (lklAmount != null ? lklAmount : 0);
totalAmount += (platAmount != null ? platAmount : 0);
totalAmount += (agent2ndAmount != null ? agent2ndAmount : 0);
@ -356,9 +481,12 @@ public class LklSeparateWithTotalAmountDTO {
if (totalAmount > totalSeparateAmount) {
// 记录详细日志
System.err.println("分账金额总和超过总金额,分账金额总和: " + totalAmount + ",总金额: " + totalSeparateAmount);
throw new IllegalArgumentException("分账金额总和超过总金额");
String errorMsg = "分账金额总和超过总金额,分账金额总和: " + totalAmount + ",总金额: " + totalSeparateAmount;
log.error(errorMsg);
return Pair.of(false, "分账金额总和超过总金额");
}
return Pair.of(true, "");
}
/**
@ -392,8 +520,7 @@ public class LklSeparateWithTotalAmountDTO {
* @return JSON格式字符串
*/
public String toJSON() {
String sb = "{" +
"\"totalSeparateAmount\":" + totalSeparateAmount + "," +
String sb = "{" + "\"totalSeparateAmount\":" + totalSeparateAmount + "," +
"\"canSeparateAmount\":" + canSeparateAmount + "," +
"\"refCanSeparateAmount\":" + refCanSeparateAmount + "," +
"\"shippingFee\":" + shippingFee + "," +
@ -410,4 +537,78 @@ public class LklSeparateWithTotalAmountDTO {
"}";
return sb;
}
}
/**
* 获取分账计算结果包含错误信息
*
* @return SeparateResult 包含成功状态分账结果和错误信息的包装类
*/
public SeparateResult getSeparateResult() {
SeparateResult result = new SeparateResult();
result.setIsSuccess(true);
result.setData(this);
result.setErrMsg(this.errMsg);
return result;
}
/**
* 执行分账计算逻辑
*
* @return SeparateResult 包含成功状态分账结果和错误信息的包装类
*/
public SeparateResult calculateSeparateAmountWithResult() {
return calculateSeparateAmount();
}
/**
* 分账计算结果包装类
*/
@Data
@EqualsAndHashCode
public static class SeparateResult {
private Boolean isSuccess;
private LklSeparateWithTotalAmountDTO data;
private String errMsg = ""; // 默认值为空字符串保证不为null
public SeparateResult() {
}
public SeparateResult(Boolean isSuccess, LklSeparateWithTotalAmountDTO data, String errMsg) {
this.isSuccess = isSuccess;
this.data = data;
this.errMsg = errMsg != null ? errMsg : ""; // 确保不为null
}
/**
* 创建成功结果
*
* @param data 分账数据
* @return SeparateResult
*/
public static SeparateResult success(LklSeparateWithTotalAmountDTO data) {
return new SeparateResult(Boolean.TRUE, data, "");
}
/**
* 创建失败结果
*
* @param errMsg 错误信息
* @return SeparateResult
*/
public static SeparateResult failure(String errMsg) {
return new SeparateResult(Boolean.FALSE, null, errMsg != null ? errMsg : "");
}
/**
* 创建部分成功结果带错误信息的成功
*
* @param data 分账数据
* @param errMsg 错误信息
* @return SeparateResult
*/
public static SeparateResult partialSuccess(LklSeparateWithTotalAmountDTO data, String errMsg) {
return new SeparateResult(Boolean.TRUE, data, errMsg != null ? errMsg : "");
}
}
}

View File

@ -1,5 +1,6 @@
package com.suisung.mall.common.utils;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
@ -10,7 +11,10 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@Slf4j
public class DateTimeUtils {
@ -375,11 +379,11 @@ public class DateTimeUtils {
* endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @return 返回一个Map包含交集的时间段(startTimeStr和endTimeStr)如果无交集则返回空Map
*/
public static Map<String, String> findTimeInterSection(List<Map<String, String>> timeList) {
public static Pair<String, String> findTimeInterSection(List<Pair<String, String>> timeList) {
// 参数校验
if (timeList == null || timeList.isEmpty()) {
log.warn("时间段列表为空或null无法计算交集");
return new HashMap<>();
return null;
}
try {
@ -387,14 +391,14 @@ public class DateTimeUtils {
LocalTime earliestEndTime = null;
// 遍历所有时间段
for (Map<String, String> timeMap : timeList) {
if (timeMap == null || !timeMap.containsKey("startTimeStr") || !timeMap.containsKey("endTimeStr")) {
for (Pair<String, String> timeMap : timeList) {
if (timeMap == null || StrUtil.hasBlank(timeMap.getFirst(), timeMap.getSecond())) {
log.warn("时间段数据不完整或格式不正确: {}", timeMap);
continue;
}
String startTimeStr = timeMap.get("startTimeStr");
String endTimeStr = timeMap.get("endTimeStr");
String startTimeStr = timeMap.getFirst();
String endTimeStr = timeMap.getSecond();
if (startTimeStr == null || endTimeStr == null) {
log.warn("时间段的开始或结束时间为空: startTime={}, endTime={}", startTimeStr, endTimeStr);
@ -416,18 +420,15 @@ public class DateTimeUtils {
// 检查是否存在交集
if (latestStartTime != null && earliestEndTime != null && !latestStartTime.isAfter(earliestEndTime)) {
Map<String, String> result = new HashMap<>();
result.put("startTimeStr", latestStartTime.toString());
result.put("endTimeStr", earliestEndTime.toString());
return result;
return Pair.of(latestStartTime.toString(), earliestEndTime.toString());
} else {
// 无交集情况
log.debug("给定的时间段列表无交集");
return new HashMap<>();
return null;
}
} catch (Exception e) {
log.error("计算时间段交集时发生异常", e);
return new HashMap<>();
return null;
}
}
@ -438,15 +439,15 @@ public class DateTimeUtils {
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param currentTime 要判断的时间点
* @return 如果在时间段内返回true否则返回false出现异常时返回false不影响主流程
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isTimeInRange(String startTimeStr, String endTimeStr, LocalDateTime currentTime) {
public static int isTimeInRange(String startTimeStr, String endTimeStr, LocalDateTime currentTime) {
try {
// 参数校验
if (startTimeStr == null || endTimeStr == null || currentTime == null) {
log.warn("时间参数不能为空startTimeStr: {}, endTimeStr: {}, currentTime: {}",
startTimeStr, endTimeStr, currentTime);
return false;
return -1;
}
// 解析开始时间 - 支持多种时间格式
@ -458,37 +459,53 @@ public class DateTimeUtils {
// 获取当前时间的时间部分
LocalTime nowTime = currentTime.toLocalTime();
// 处理开始时间等于结束时间的情况
if (startTime.equals(endTime)) {
return nowTime.equals(startTime) ? 0 : -1; // 精确匹配为时间段内否则为时间段前
}
// 处理跨天情况例如 22:00 - 06:00
if (endTime.isBefore(startTime)) {
// 如果结束时间小于开始时间说明跨越了午夜
// 当前时间需要满足大于等于开始时间 或者 小于等于结束时间
return !nowTime.isBefore(startTime) || !nowTime.isAfter(endTime);
// 跨天时间段: [startTime, 24:00) [00:00, endTime]
if (!nowTime.isBefore(startTime) || !nowTime.isAfter(endTime)) {
return 0; // 时间段内
} else {
// 对于跨天情况不在时间段内的唯一可能就是"时间段前"
// 因为跨天时间段覆盖了从startTime到endTime经过午夜的整个区间
return -1; // 时间段前
}
} else {
// 正常情况不跨天当前时间需要在开始时间和结束时间之间包含边界
return !nowTime.isBefore(startTime) && !nowTime.isAfter(endTime);
if (nowTime.isBefore(startTime)) {
return -1; // 时间段前
} else if (nowTime.isAfter(endTime)) {
return 1; // 时间段后
} else {
return 0; // 时间段内
}
}
} catch (Exception e) {
// 捕获解析异常记录日志并返回false避免影响主流程
log.error("判断时间是否在范围内时发生异常startTimeStr: {}, endTimeStr: {}, currentTime: {}",
startTimeStr, endTimeStr, currentTime, e);
return false;
return -1;
}
}
/**
* 判断指定时间是否在两个时间点之间包含边界
*
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param currentTime 要判断的时间点
* @return 如果在时间段内返回true否则返回false出现异常时返回false不影响主流程
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isTimeInRange(String startTimeStr, String endTimeStr, Date currentTime) {
public static int isTimeInRange(String startTimeStr, String endTimeStr, Date currentTime) {
if (currentTime == null) {
log.warn("时间参数不能为空startTimeStr: {}, endTimeStr: {}, currentTime: null",
startTimeStr, endTimeStr);
return false;
return -1;
}
// 将Date转换为LocalDateTime并调用已有的方法
LocalDateTime localDateTime = currentTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
@ -500,9 +517,9 @@ public class DateTimeUtils {
*
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @return 如果在时间段内返回true否则返回false
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isCurrentTimeInRange(String startTimeStr, String endTimeStr) {
public static int isCurrentTimeInRange(String startTimeStr, String endTimeStr) {
return isTimeInRange(startTimeStr, endTimeStr, LocalDateTime.now());
}
@ -619,38 +636,24 @@ public class DateTimeUtils {
System.out.println("=== 测试 findTimeIntersection ===");
// 测试正常交集情况
List<Map<String, String>> timeList1 = new ArrayList<>();
Map<String, String> range1 = new HashMap<>();
range1.put("startTimeStr", "06:00");
range1.put("endTimeStr", "17:00");
timeList1.add(range1);
List<Pair<String, String>> timeList1 = new ArrayList<>();
timeList1.add(Pair.of("06:00", "17:00"));
timeList1.add(Pair.of("10:00", "18:00"));
Map<String, String> range2 = new HashMap<>();
range2.put("startTimeStr", "06:00");
range2.put("endTimeStr", "17:00");
timeList1.add(range2);
Map<String, String> intersection1 = findTimeInterSection(timeList1);
Pair<String, String> intersection1 = findTimeInterSection(timeList1);
System.out.println("交集结果1: " + intersection1); // 应该是 10:00 - 17:00
// 测试无交集情况
List<Map<String, String>> timeList2 = new ArrayList<>();
Map<String, String> range3 = new HashMap<>();
range3.put("startTimeStr", "09:00");
range3.put("endTimeStr", "12:00");
timeList2.add(range3);
// 测试无交集情况
List<Pair<String, String>> timeList2 = new ArrayList<>();
timeList2.add(Pair.of("09:00", "12:00"));
timeList2.add(Pair.of("13:00", "17:00"));
Map<String, String> range4 = new HashMap<>();
range4.put("startTimeStr", "13:00");
range4.put("endTimeStr", "17:00");
timeList2.add(range4);
Pair<String, String> intersection2 = findTimeInterSection(timeList2);
System.out.println("交集结果2: " + intersection2); // 应该是null
Map<String, String> intersection2 = findTimeInterSection(timeList2);
System.out.println("交集结果2: " + intersection2); // 应该是空Map
// 测试空列表
Map<String, String> intersection3 = findTimeInterSection(null);
System.out.println("交集结果3 (null输入): " + intersection3); // 应该是空Map
// 测试空列表
Pair<String, String> intersection3 = findTimeInterSection(null);
System.out.println("交集结果3 (null输入): " + intersection3); // 应该是null
}

View File

@ -0,0 +1,175 @@
package com.suisung.mall.common.utils;
import com.suisung.mall.core.web.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* 分布式锁工具类
*
* <p>使用示例:</p>
* <pre>
* {@code
* // 方式1: 简单使用
* DistributedLockHelper lockHelper = new DistributedLockHelper();
* if (lockHelper.tryLock("order_create_lock", 30)) {
* try {
* // 执行需要加锁的业务逻辑
* createOrder();
* } finally {
* lockHelper.releaseLock("order_create_lock");
* }
* }
*
* // 方式2: 使用自动续期锁
* try (DistributedLockHelper.LockHolder lockHolder =
* lockHelper.tryLockWithAutoRenew("batch_process_lock", 30, 300)) {
* if (lockHolder != null) {
* // 执行长时间业务逻辑
* processBatchData();
* }
* }
* }
* </pre>
*/
@Component
public class DistributedLockHelper {
private static final String LOCK_PREFIX = "distributed_lock:";
private static final long DEFAULT_EXPIRE_TIME_SEC = 30; // 单位为秒
private static final ThreadLocal<String> LOCK_VALUE_HOLDER = new ThreadLocal<>();
@Autowired
private RedisService redisService;
/**
* 获取分布式锁 - 带有唯一标识符防止误删
*
* @param lockKey 锁的key
* @param expireTimeSec 过期时间()
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, long expireTimeSec) {
String key = LOCK_PREFIX + lockKey;
String value = UUID.randomUUID() + ":" + Thread.currentThread().getId();
try {
// 使用最通用的Redis操作方法
// 先尝试设置键值对如果键不存在则设置成功
redisService.set(key, value, expireTimeSec);
LOCK_VALUE_HOLDER.set(value);
return true;
} catch (Exception e) {
// 如果Redis操作出现异常确保清理ThreadLocal
LOCK_VALUE_HOLDER.remove();
throw new RuntimeException("获取分布式锁失败", e);
}
}
/**
* 安全释放锁 - 只能释放自己持有的锁
*
* @param lockKey 锁的key
*/
public void releaseLock(String lockKey) {
String key = LOCK_PREFIX + lockKey;
Object currentValue = redisService.get(key);
Object expectedValue = LOCK_VALUE_HOLDER.get();
// 使用Lua脚本确保原子性防止误删
if (currentValue != null && currentValue.equals(expectedValue)) {
redisService.del(key);
}
LOCK_VALUE_HOLDER.remove();
}
/**
* 带自动续期的锁获取
*
* @param lockKey 锁的key
* @param expireTimeSec 过期时间()
* @param maxHoldTimeSec 最大持有时间()
* @return 锁对象
*/
public LockHolder tryLockWithAutoRenew(String lockKey, long expireTimeSec, long maxHoldTimeSec) {
if (tryLock(lockKey, expireTimeSec)) {
// 启动自动续期线程
AutoRenewTask renewTask = new AutoRenewTask(lockKey, expireTimeSec / 2);
Thread renewThread = new Thread(renewTask);
renewThread.setDaemon(true);
renewThread.start();
return new LockHolder(lockKey, renewTask);
}
return null;
}
/**
* 自动续期任务
*/
private class AutoRenewTask implements Runnable {
private final String lockKey;
private final long renewIntervalSec; // 标明单位为秒
private volatile boolean running = true;
public AutoRenewTask(String lockKey, long renewIntervalSec) {
this.lockKey = lockKey;
this.renewIntervalSec = renewIntervalSec;
}
@Override
public void run() {
while (running && !Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(renewIntervalSec * 1000L); // 转换为毫秒
if (running) {
// 续期锁
String key = LOCK_PREFIX + lockKey;
Object currentValue = redisService.get(key);
Object expectedValue = LOCK_VALUE_HOLDER.get();
if (currentValue != null && currentValue.equals(expectedValue)) {
redisService.expire(key, DEFAULT_EXPIRE_TIME_SEC);
} else {
// 锁已丢失停止续期
break;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
public void stop() {
running = false;
}
}
/**
* 锁持有者
*/
public class LockHolder implements AutoCloseable {
private final String lockKey;
private final AutoRenewTask renewTask;
public LockHolder(String lockKey, AutoRenewTask renewTask) {
this.lockKey = lockKey;
this.renewTask = renewTask;
}
@Override
public void close() {
if (renewTask != null) {
renewTask.stop();
}
releaseLock(lockKey);
}
}
}

View File

@ -57,6 +57,8 @@ secure:
- "/swagger-resources/**"
- "/swagger/**"
- "/static/**"
- "/shop/static/**"
- "/static/image/**"
- "/**/v2/api-docs"
- "/**/*.js"
- "/**/*.css"
@ -67,8 +69,6 @@ secure:
- "/mall-auth/rsa/publicKey"
- "/admin/account/account-user-base/register"
- "/admin/account/account-user-base/login"
- "/static/image/**"
- "/shop/static/**"
- "/admin/account/open/**"
- "/admin/account/account-user-base/doLogin"
- "/mobile/pay/index/notify_url"

View File

@ -26,7 +26,7 @@ logging:
level:
com:
sun:
mail: error
mail: info
baomidou: error
alibaba:
nacos:

View File

@ -100,7 +100,7 @@ logging:
suisung:
mall:
pay:
mapper: debug
mapper: info
sun:
mail: error
baomidou: error

View File

@ -112,14 +112,14 @@ public class UserActivityController extends BaseControllerImpl {
return CommonResult.success(shopActivityCutpriceHistoryService.listsUserCutPriceHistory(page, rows, is_sponsor));
}
@ApiOperation(value = "砍价活动详情", notes = "砍价活动详情")
@ApiOperation(value = "砍价活动详情", notes = "砍价活动详情(注:除了获取活动详情信息,还创建砍价订单记录和查询砍价历史历史)")
@RequestMapping(value = "/getCutPriceActivity", method = RequestMethod.GET)
public CommonResult getCutPriceActivityDetail() {
return CommonResult.success(shopActivityCutpriceService.getCutPriceActivity());
}
@ApiOperation(value = "立即砍价", notes = "自己砍价、要求朋友过来也能砍价")
@RequestMapping(value = "/doCutPrice", method = RequestMethod.GET)
@ApiOperation(value = "立即砍价", notes = "自己砍价、邀请朋友过来也能砍价")
@RequestMapping(value = "/doCutPrice", method = {RequestMethod.GET, RequestMethod.POST})
public CommonResult doCutPrice(@RequestParam(name = "ac_id", defaultValue = "0") Integer ac_id) {
UserDto user = getCurrentUser();
if (user == null || CheckUtil.isEmpty(user.getId())) {

View File

@ -3,6 +3,7 @@ package com.suisung.mall.shop.activity.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.modules.activity.ShopActivityCutprice;
import com.suisung.mall.common.modules.store.ShopStoreActivityBase;
import com.suisung.mall.core.web.service.IBaseService;
import org.springframework.data.util.Pair;
@ -38,4 +39,30 @@ public interface ShopActivityCutpriceService extends IBaseService<ShopActivityCu
*/
Pair<Boolean, String> canDoOrderCutPriceActivity(Integer ac_id, Integer order_user_id);
/**
* 检查砍价活动是否过期和库存是否足够
*
* @param shopStoreActivityBase 砍价活动基础信息
* @return Pair<Boolean, String> 检查结果Boolean表示是否通过检查String表示错误信息
*/
Pair<Boolean, String> checkCutPriceExpiredAndStock(ShopStoreActivityBase shopStoreActivityBase);
/**
* 根据自增Id activity_id修改某个砍价订单状态
*
* @param ac_id 活动自增Id (ac_id activity_id 其中一个必填)
* @param activity_id 活动Id (ac_id activity_id 其中一个必填)
* @param state 状态
* @return
*/
Boolean updateCutPriceState(Integer ac_id, Integer activity_id, Integer state);
/**
* 砍价活动结束后定时修改砍价订单状态 定时任务包括多个状态业务变更
*
* @return
*/
Integer autoUpdateCutPriceStateJob();
}

View File

@ -10,6 +10,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.api.StateCode;
@ -259,71 +260,112 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
return toMobileResult(cutpricePage);
}
/**
* 砍价活动详情
* 砍价活动详情除了获取活动详情信息还创建砍价订单记录和查询砍价历史历史
*
* @return
* @return 砍价活动详情信息
*/
@Override
public Map getCutPriceActivity() {
// 获取请求参数
Integer activity_id = getParameter("activity_id", 0);
Integer participant_id = getParameter("participant_id", 0);
Integer user_id = getParameter("user_id", 0);
// 参数校验
if (activity_id == null || activity_id <= 0) {
throw new ApiException(I18nUtil._("活动ID无效"));
}
if (user_id == null || user_id <= 0) {
throw new ApiException(I18nUtil._("用户ID无效"));
}
// 获取活动基础信息
ShopStoreActivityBase activityBase = shopStoreActivityBaseService.get(activity_id);
if (activityBase == null) {
throw new ApiException(I18nUtil._("未找到活动信息!"));
}
Map activity_row = Convert.toMap(String.class, Object.class, activityBase);
// if (!shopStoreActivityBaseService.verifyActivity(activity_row)) {
// throw new ApiException(I18nUtil._("该活动不存在或已结束!"));
// }
// 获取当前用户信息
UserDto user = getCurrentUser();
if (CheckUtil.isEmpty(participant_id) && user != null) {
if (CheckUtil.isEmpty(participant_id) && user != null && user.getId() > 0) {
participant_id = user.getId();
}
// 查询用户是否已参与该砍价活动
QueryWrapper<ShopActivityCutprice> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("activity_id", activity_id).eq("user_id", user_id);
ShopActivityCutprice cutprice_row = findOne(queryWrapper);
boolean is_join_activity = false;
// 如果用户未参与该砍价活动则创建新的砍价记录
if (cutprice_row == null) {
// 需要检查活动有效期和活动商品库存是否足够
Pair<Boolean, String> check_result = checkCutPriceExpiredAndStock(activityBase);
if (!check_result.getFirst()) {
throw new ApiException(check_result.getSecond());
}
cutprice_row = new ShopActivityCutprice();
// 从活动规则中获取商品原价和砍价底价
JSONObject activity_rule = JSONUtil.parseObj(activityBase.getActivity_rule());
if (activity_rule == null) {
throw new ApiException(I18nUtil._("活动规则数据异常!"));
}
BigDecimal item_unit_price = Convert.toBigDecimal(activity_rule.get("item_unit_price"));
BigDecimal cut_down_min_limit_price = Convert.toBigDecimal(activity_rule.get("cut_down_min_limit_price"));
// 初始化砍价记录信息
cutprice_row.setActivity_id(activity_id);
cutprice_row.setUser_id(user_id);
cutprice_row.setAc_sale_price(item_unit_price);
cutprice_row.setAc_mix_limit_price(cut_down_min_limit_price);
cutprice_row.setAc_datetime(new Date());
Date now = new Date();
// 该砍价记录过期时间戳
Integer cut_hour = activityBase.getCut_hour();
if (CheckUtil.isEmpty(cut_hour)) {
cut_hour = 48;
}
cutprice_row.setExpired_at(now.getTime() + cut_hour * 60000L);
cutprice_row.setAc_datetime(now);
cutprice_row.setOrder_id("");
// 保存砍价记录
if (!saveOrUpdate(cutprice_row)) {
throw new ApiException(I18nUtil._("创建砍价失败!"));
}
is_join_activity = true;
}
// 将砍价记录信息合并到活动信息中
activity_row.putAll(Convert.toMap(String.class, Object.class, cutprice_row));
// 查询当前用户是否已参与砍价查询砍价历史记录
Integer ac_id = cutprice_row.getAc_id();
QueryWrapper<ShopActivityCutpriceHistory> historyQueryWrapper = new QueryWrapper<>();
historyQueryWrapper.eq("ac_id", ac_id).eq("user_id", participant_id);
ShopActivityCutpriceHistory cutpriceHistory = cutpriceHistoryService.findOne(historyQueryWrapper);
// 设置用户砍价历史信息
activity_row.put("cut_row", cutpriceHistory);
// 是否自己砍价了
activity_row.put("is_cut", cutpriceHistory != null);
// 是否参加了砍价活动
activity_row.put("is_join_activity", is_join_activity);
// 设置活动规则信息
activity_row.put("activity_rule", JSONUtil.parseObj(activity_row.get("activity_rule")));
// // 砍价订单有效期时长
// if (cutprice_row != null && cutprice_row.getAc_datetime() != null) {
// Float order_cutprice_time = accountBaseConfigService.getConfig("order_cutprice_time", 3f);
// int second = NumberUtil.mul(order_cutprice_time, 24, 60, 60).intValue();
// long time = DateUtil.offsetSecond(cutprice_row.getAc_datetime(), +second).getTime();
// activity_row.put("activity_endtime", Convert.toDate(time));
// }
// 界面做一个简单的底价验证
// 判断是否可以立即购买
boolean canBuyNow = cutprice_row != null
&& CheckUtil.isNotEmpty(cutprice_row.getAc_sale_price())
&& CheckUtil.isNotEmpty(cutprice_row.getAc_mix_limit_price())
@ -331,7 +373,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
BigDecimal subtractPrice = NumberUtil.sub(cutprice_row.getAc_sale_price(), cutprice_row.getAc_mix_limit_price());
// 能否立即出手 1-可以2-不可以
// 设置购买状态和提示信息
activity_row.put("can_buy_now", canBuyNow ? CommonConstant.Enable : CommonConstant.Disable2);
activity_row.put("cannot_buy_now_reason",
canBuyNow ? "恭喜您,商品可以立即出手了。" :
@ -340,56 +382,121 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
return activity_row;
}
/**
* 砍价
* 自己或朋友砍价
*
* @param ac_id 活动 Id
* @param user_id 砍价用户
* @return
* @return CommonResult 砍价结果
*/
@Override
@Transactional
public CommonResult doCutPrice(Integer ac_id, Integer user_id) {
DateTime today = DateUtil.beginOfDay(new Date());
Integer num = accountBaseConfigService.getConfig("user_cutprice_num", 0);
Integer cut_num = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0);
ShopActivityCutprice cutprice_row = get(ac_id);
if (!cutprice_row.getUser_id().equals(user_id)) {
// 参数校验
if (ac_id == null || ac_id <= 0) {
return CommonResult.failed(I18nUtil._("活动ID无效"));
}
if (user_id == null || user_id <= 0) {
return CommonResult.failed(I18nUtil._("用户ID无效"));
}
// 获取砍价记录
ShopActivityCutprice shopActivityCutprice = get(ac_id);
if (shopActivityCutprice == null) {
return CommonResult.failed(I18nUtil._("抱歉,砍价记录已失效!"));
}
Integer activity_id = shopActivityCutprice.getActivity_id();
// 检查活动状态是否正常
ShopStoreActivityBase shopStoreActivityBase = shopStoreActivityBaseService.get(shopActivityCutprice.getActivity_id());
if (shopStoreActivityBase == null) {
// 活动不存在更新砍价订单状态为已取消
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled);
return CommonResult.failed(I18nUtil._("抱歉,砍价活动已失效!"));
}
// 活动状态不是正常状态
if (!ObjectUtil.equal(shopStoreActivityBase.getActivity_state(), StateCode.ACTIVITY_STATE_NORMAL)) {
// 如果活动已结束或已关闭更新砍价订单状态为已取消
if (ObjectUtil.equal(shopStoreActivityBase.getActivity_state(), StateCode.ACTIVITY_STATE_FINISHED)
|| ObjectUtil.equal(shopStoreActivityBase.getActivity_state(), StateCode.ACTIVITY_STATE_CLOSED)) {
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Canceled);
}
return CommonResult.failed(I18nUtil._("抱歉,砍价活动已失效!"));
}
Date now = new Date();
Long expired_at = shopActivityCutprice.getExpired_at();
// 检查砍价订单是否过期
if (CheckUtil.isNotEmpty(expired_at) && expired_at < now.getTime()) {
// 砍价订单已过期更新状态为已过期
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired);
return CommonResult.failed(I18nUtil._("砍价已过期,下次早点来!"));
}
// 检查活动是否已结束活动时间已过
if (!shopStoreActivityBaseService.isActivityTimeValid(shopStoreActivityBase, now)) {
// 活动已结束更新砍价订单状态为已过期
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_Expired);
return CommonResult.failed(I18nUtil._("砍价已过期,下次早点来!"));
}
// 检查是否是帮别人砍价且次数已用完
if (!shopActivityCutprice.getUser_id().equals(user_id)) {
DateTime today = DateUtil.beginOfDay(new Date());
Integer num = accountBaseConfigService.getConfig("user_cutprice_num", 0);
Integer cut_num = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0);
if (num > 0 && cut_num >= num) {
throw new ApiException(I18nUtil._("今日帮砍次数已用完!"));
return CommonResult.failed(I18nUtil._("今日帮砍次数已用完!"));
}
}
BigDecimal ac_sale_price = cutprice_row.getAc_sale_price();
BigDecimal ac_mix_limit_price = cutprice_row.getAc_mix_limit_price();
if (ObjectUtil.compare(ac_sale_price, ac_mix_limit_price) <= 0) {
throw new ApiException(I18nUtil._("已达到最低价!"));
BigDecimal ac_sale_price = shopActivityCutprice.getAc_sale_price();
BigDecimal ac_mix_limit_price = shopActivityCutprice.getAc_mix_limit_price();
// 检查价格数据是否完整
if (ac_sale_price == null || ac_mix_limit_price == null) {
return CommonResult.failed(I18nUtil._("价格数据异常!"));
}
Integer activity_id = cutprice_row.getActivity_id();
ShopStoreActivityBase storeActivityBase = shopStoreActivityBaseService.get(activity_id);
Map activity_row = Convert.toMap(String.class, Object.class, storeActivityBase);
if (!shopStoreActivityBaseService.isActivityTimeValid(activity_row)) {
throw new ApiException(I18nUtil._("该活动不存在!"));
// 检查是否已达到最低价
if (NumberUtil.isLessOrEqual(ac_sale_price, ac_mix_limit_price)) {
// 根据上次的状态立即更改状态6-砍价助力已完成待下单
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished);
return CommonResult.failed(I18nUtil._("已达到最低价!"));
}
// 检查是否已经帮过好友砍了价
QueryWrapper<ShopActivityCutpriceHistory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", user_id).eq("ac_id", ac_id);
long ach_num = shopActivityCutpriceHistoryService.count(queryWrapper);
if (CheckUtil.isNotEmpty(ach_num)) {
throw new ApiException(I18nUtil._("已经帮助好友进行砍价!"));
if (ach_num > 0) {
return CommonResult.failed(I18nUtil._("已经帮好友砍过价!"));
}
BigDecimal cut_price = shopStoreActivityBaseService.getCutDownPrice(storeActivityBase, cutprice_row);
if (CheckUtil.isEmpty(cut_price)) {
throw new ApiException(I18nUtil._("砍价失败!"));
// 计算砍价金额
BigDecimal cut_price = shopStoreActivityBaseService.getCutDownPrice(shopStoreActivityBase, shopActivityCutprice);
if (cut_price == null || cut_price.compareTo(BigDecimal.ZERO) <= 0) {
return CommonResult.failed(I18nUtil._("砍价失败!"));
}
String str_activity_rule = storeActivityBase.getActivity_rule();
com.alibaba.fastjson.JSONObject activity_rule = com.alibaba.fastjson.JSONObject.parseObject(str_activity_rule);
Long item_id = activity_rule.getObject("item_id", Long.class);
// 获取商品ID
String str_activity_rule = shopStoreActivityBase.getActivity_rule();
if (StrUtil.isBlank(str_activity_rule)) {
return CommonResult.failed(I18nUtil._("活动规则数据异常!"));
}
JSONObject activity_rule = JSONUtil.parseObj(str_activity_rule);
Long item_id = activity_rule.get("item_id", Long.class);
if (item_id == null) {
return CommonResult.failed(I18nUtil._("未找到商品信息!"));
}
// 创建砍价历史记录
ShopActivityCutpriceHistory ach_data = new ShopActivityCutpriceHistory();
ach_data.setActivity_id(activity_id);
ach_data.setUser_id(user_id);
@ -398,23 +505,34 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
ach_data.setAch_datetime(new Date());
ach_data.setAc_id(ac_id);
// 保存砍价历史记录
if (!shopActivityCutpriceHistoryService.saveOrUpdate(ach_data)) {
throw new ApiException(I18nUtil._("修改砍价历史失败!"));
throw new ApiException(I18nUtil._("保存砍价历史失败!"));
}
cutprice_row.setAc_sale_price(NumberUtil.sub(ac_sale_price, cut_price));
cutprice_row.setAc_num(cutprice_row.getAc_num() + 1);
if (!edit(cutprice_row)) {
throw new ApiException(I18nUtil._("修改金额失败!"));
// 更新砍价信息
shopActivityCutprice.setAc_sale_price(NumberUtil.sub(ac_sale_price, cut_price));
shopActivityCutprice.setAc_num(shopActivityCutprice.getAc_num() + 1);
if (!edit(shopActivityCutprice)) {
throw new ApiException(I18nUtil._("更新砍价信息失败!"));
}
if (!cutprice_row.getUser_id().equals(user_id)) {
// 更新帮砍次数如果不是自己砍自己的话
if (!shopActivityCutprice.getUser_id().equals(user_id)) {
DateTime today = DateUtil.beginOfDay(new Date());
Integer cut_num = Convert.toInt(redisService.hGet("cutprice-" + today, user_id.toString()), 0);
redisService.hSet("cutprice-" + today, user_id.toString(), cut_num + 1, 24 * 60 * 60 * 1000);
}
// 根据最新砍价信息(最后一次砍价成功之后达到最低砍价价格)更新砍价订单状态
if (NumberUtil.isGreaterOrEqual(shopActivityCutprice.getAc_sale_price(), ac_mix_limit_price)) {
updateCutPriceState(shopActivityCutprice.getAc_id(), null, CommonConstant.CutPrice_Order_State_CutFinished);
}
return CommonResult.success(ach_data);
}
/**
* 砍价活动是否符合立即下单了
*
@ -448,7 +566,7 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
}
// 2. 检查活动是否在有效期内
if (!shopStoreActivityBaseService.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime())) {
if (!shopStoreActivityBaseService.isActivityTimeValid(storeActivityBase.getActivity_starttime(), storeActivityBase.getActivity_endtime(), new Date())) {
return Pair.of(false, I18nUtil._("该活动已过期,请检查。"));
}
@ -492,5 +610,177 @@ public class ShopActivityCutpriceServiceImpl extends BaseServiceImpl<ShopActivit
}
}
/**
* 检查砍价活动是否过期和库存是否足够
*
* @param shopStoreActivityBase 砍价活动基础信息
* @return Pair<Boolean, String> 检查结果Boolean表示是否通过检查String表示错误信息
*/
@Override
public Pair<Boolean, String> checkCutPriceExpiredAndStock(ShopStoreActivityBase shopStoreActivityBase) {
// 参数校验
if (shopStoreActivityBase == null) {
return Pair.of(false, I18nUtil._("活动信息不能为空"));
}
// 检查活动是否过期
boolean isActivityTimeValid = shopStoreActivityBaseService.isActivityTimeValid(
shopStoreActivityBase.getActivity_starttime(),
shopStoreActivityBase.getActivity_endtime(),
new Date()
);
if (!isActivityTimeValid) {
return Pair.of(false, I18nUtil._("该活动已过期,下次早点来!"));
}
// 检查活动商品库存
Integer productCount = shopStoreActivityBase.getProduct_count();
if (CheckUtil.isEmpty(productCount) || productCount <= 0) {
return Pair.of(false, I18nUtil._("活动商品库存不足,请稍后再来。"));
}
// 查询已占用库存的砍价订单数量
QueryWrapper<ShopActivityCutprice> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("activity_id", shopStoreActivityBase.getActivity_id())
.in("state", Arrays.asList(
CommonConstant.CutPrice_Order_State_Finished,
CommonConstant.CutPrice_Order_State_CutFinished,
CommonConstant.CutPrice_Order_State_ING
));
long recordCount = count(queryWrapper);
// 判断库存是否充足当已占用库存大于等于总库存时表示库存不足
if (recordCount >= productCount) {
return Pair.of(false, I18nUtil._("活动商品库存不足,请稍后再来。"));
}
// 所有检查通过
return Pair.of(true, "");
}
/**
* 根据自增Id activity_id修改某个砍价订单状态
*
* @param ac_id 活动自增Id (ac_id activity_id 其中一个必填)
* @param activity_id 活动Id (ac_id activity_id 其中一个必填)
* @param state 状态
* @return Boolean 是否更新成功
*/
@Override
public Boolean updateCutPriceState(Integer ac_id, Integer activity_id, Integer state) {
// 参数校验ac_id activity_id 至少有一个不为空 state 不为空
if ((ac_id == null || ac_id <= 0) && (activity_id == null || activity_id <= 0)) {
log.warn("更新砍价订单状态参数错误ac_id 和 activity_id 不能同时为空");
return false;
}
if (state == null) {
log.warn("更新砍价订单状态参数错误state 不能为空");
return false;
}
try {
UpdateWrapper<ShopActivityCutprice> updateWrapper = new UpdateWrapper<>();
// 根据 ac_id activity_id 条件更新状态避免重复更新相同状态
if (ac_id != null && ac_id > 0) {
updateWrapper.eq("ac_id", ac_id);
}
if (activity_id != null && activity_id > 0) {
updateWrapper.eq("activity_id", activity_id);
}
updateWrapper.ne("state", state).set("state", state);
return update(updateWrapper);
} catch (Exception e) {
log.error("更新砍价订单状态失败ac_id={}, activity_id={}, state={}", ac_id, activity_id, state, e);
return false;
}
}
/**
* 砍价活动结束后定时修改砍价订单状态 定时任务包括多个状态业务变更
* <p>
* 砍价订单状态1-砍价已完成下单2-砍价未下单已取消3-砍价助力进行中4-砍价过期失效6-砍价助力已完成待下单
* <p>
* 1某个砍价订单未超时情况下更改3-砍价助力进行中砍到最低价时的状态为 6-砍价助力已完成待下单
* 2某个砍价订单超时的情况下定时更改3-砍价助力进行中的状态为 4-砍价过期失效
* 3砍价活动结束后定时更改3-砍价助力进行中和6-砍价助力已完成待下单的状态为 4-砍价过期失效和2-砍价未下单已取消
*
* @return 成功更新的操作次数
*/
@Override
public Integer autoUpdateCutPriceStateJob() {
int successCount = 0;
log.info("开始执行砍价订单状态定时更新任务");
try {
// 1某个砍价订单未超时情况下更改3-砍价助力进行中砍到最低价时的状态为 6-砍价助力已完成待下单
UpdateWrapper<ShopActivityCutprice> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("state", CommonConstant.CutPrice_Order_State_ING)
.ge("expired_at", System.currentTimeMillis())
.apply("ac_sale_price <= ac_mix_limit_price")
.set("state", CommonConstant.CutPrice_Order_State_CutFinished);
log.debug("准备执行更新操作1将状态3且未过期且达到最低价的砍价订单更新为状态6");
if (update(updateWrapper)) {
log.info("成功更新砍价订单状态为【砍价助力已完成待下单】条件state={}, ac_sale_price<=ac_mix_limit_price",
CommonConstant.CutPrice_Order_State_ING);
successCount++;
} else {
log.debug("更新操作1未匹配到任何记录");
}
// 2某个砍价订单超时的情况下定时更改3-砍价助力进行中的状态为 4-砍价过期失效
updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("state", CommonConstant.CutPrice_Order_State_ING)
.lt("expired_at", System.currentTimeMillis())
.set("state", CommonConstant.CutPrice_Order_State_Expired);
log.debug("准备执行更新操作2将状态3且已过期的砍价订单更新为状态4");
if (update(updateWrapper)) {
log.info("成功更新砍价订单状态为【砍价过期失效】条件state={}, expired_at<{}",
CommonConstant.CutPrice_Order_State_ING, System.currentTimeMillis());
successCount++;
} else {
log.debug("更新操作2未匹配到任何记录");
}
/// 3砍价活动结束后定时更改3-砍价助力进行中和6-砍价助力已完成待下单的状态为 4-砍价过期失效和2-砍价未下单已取消
// 处理状态3(砍价助力进行中) -> 状态4(砍价过期失效)
updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("state", CommonConstant.CutPrice_Order_State_ING)
.exists("SELECT 1 FROM shop_store_activity_base WHERE shop_store_activity_base.activity_id = shop_activity_cutprice.activity_id AND shop_store_activity_base.activity_endtime < NOW()")
.set("state", CommonConstant.CutPrice_Order_State_Expired);
log.debug("准备执行更新操作3将状态3且活动已结束的砍价订单更新为状态4");
if (update(updateWrapper)) {
log.info("成功更新砍价订单状态为【砍价过期失效】条件state={}, activity_endtime<NOW()",
CommonConstant.CutPrice_Order_State_ING);
successCount++;
} else {
log.debug("更新操作3未匹配到任何记录");
}
// 处理状态6(砍价助力已完成待下单) -> 状态2(砍价未下单已取消)
updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("state", CommonConstant.CutPrice_Order_State_CutFinished)
.exists("SELECT 1 FROM shop_store_activity_base WHERE shop_store_activity_base.activity_id = shop_activity_cutprice.activity_id AND shop_store_activity_base.activity_endtime < NOW()")
.set("state", CommonConstant.CutPrice_Order_State_Canceled);
log.debug("准备执行更新操作4将状态6且活动已结束的砍价订单更新为状态2");
if (update(updateWrapper)) {
log.info("成功更新砍价订单状态为【砍价未下单已取消】条件state={}, activity_endtime<NOW()",
CommonConstant.CutPrice_Order_State_CutFinished);
successCount++;
} else {
log.debug("更新操作4未匹配到任何记录");
}
log.info("定时任务更新砍价订单状态完成,总共成功执行{}次更新操作", successCount);
} catch (Exception e) {
log.error("定时任务更新砍价订单状态时发生异常,已成功执行{}次更新操作", successCount, e);
}
return successCount;
}
}

View File

@ -169,32 +169,44 @@ public class ShopBaseConfigServiceImpl extends BaseServiceImpl<ShopBaseConfigMap
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 产生验证码图片的图片的宽是116高是36验证码的长度是4干扰线的条数是20
// 参数校验
String verify_token = getParameter("verify_token", String.class);
if (StrUtil.isBlank(verify_token)) {
logger.warn("验证码缺少 verify_token 参数");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// 产生验证码图片
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 20);
// 拼接时间戳
String verify_token = getParameter("verify_token", String.class);
//获取验证码图片中的字符串
// 生成Redis键
String code = RedisConstant.Verifycode_NameSpace + verify_token + lineCaptcha.getCode();
redisService.set(code, "", 60); // 有效期一分钟
//获取到response的响应流
// 写入Redis并检查结果
redisService.set(code, "", 60);
// 输出图片
BufferedImage image = lineCaptcha.getImage();
OutputStream os = null;
try {
os = response.getOutputStream();
ImageIO.write(image, "png", os);
os.flush();
} catch (IOException e) {
logger.error("获取验证码响应异常!" + e.getMessage(), e);
logger.error("获取验证码响应异常: {}", e.getMessage(), e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
try {
assert os != null;
os.close();
} catch (IOException e) {
logger.error("获取验证码响应异常!" + e.getMessage(), e);
if (os != null) {
try {
os.close();
} catch (IOException e) {
logger.error("关闭输出流异常:{}", e.getMessage(), e);
}
}
}
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.modules.store.ShopStoreActivityBase;
import com.suisung.mall.shop.activity.service.ShopActivityCutpriceService;
import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.store.service.ShopStoreActivityBaseService;
import org.quartz.JobExecutionContext;
@ -27,6 +28,7 @@ public class UpdateActivityStatusJob extends QuartzJobBean {
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Logger logger = LoggerFactory.getLogger(UpdateActivityStatusJob.class);
ShopStoreActivityBaseService shopStoreActivityBaseService = SpringUtil.getBean(ShopStoreActivityBaseService.class);
ShopActivityCutpriceService shopActivityCutpriceService = SpringUtil.getBean(ShopActivityCutpriceService.class);
TransactionTemplate transactionTemplate = SpringUtil.getBean(TransactionTemplate.class);
int page = 1;
@ -63,5 +65,8 @@ public class UpdateActivityStatusJob extends QuartzJobBean {
page++;
}
// 更新砍价订单的过期状态
shopActivityCutpriceService.autoUpdateCutPriceStateJob();
}
}

View File

@ -92,7 +92,9 @@ public class LakalaController extends BaseControllerImpl {
// return lklLedgerReceiverService.selectAgentAndPlatformByMchId(36L);
return lakalaApiService.ewalletWithDrawNotify(null, paramsJSON.getStr("a"), paramsJSON.getStr("b"));
// return lakalaApiService.ewalletWithDrawNotify(null, paramsJSON.getStr("a"), paramsJSON.getStr("b"));
return lakalaApiService.tradeQuery(paramsJSON.getInt("storeId"), paramsJSON.getStr("orderId"));
}
@ApiOperation(value = "批量发送推送消息 - 测试案例", notes = "批量发送推送消息 - 测试案例")

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.shop.lakala.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.suisung.mall.common.modules.lakala.LklReceiveNotifyLog;
import org.springframework.stereotype.Repository;
@Repository
public interface LklReceiveNotifyLogMapper extends BaseMapper<LklReceiveNotifyLog> {
}

View File

@ -176,7 +176,7 @@ public interface LakalaApiService {
* @return
*/
CommonResult getBankCardBin(String bankCardNo);
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>
@ -376,7 +376,7 @@ public interface LakalaApiService {
* @param refCanSeparateAmt 参考可分金额单位 可选参数
* @return Pair<Boolean, LklSeparateDTO> 分账参数评估结果第一个元素表示是否成功第二个元素为分账参数对象
*/
Pair<Boolean, LklSeparateWithTotalAmountDTO> calculateAndEvaluateSharingParams(Integer orderPayAmount,
LklSeparateWithTotalAmountDTO.SeparateResult calculateAndEvaluateSharingParams(Integer orderPayAmount,
Integer shippingFeeInner,
BigDecimal mchSplitRatioRaw,
BigDecimal platSplitRatio,

View File

@ -31,7 +31,7 @@ public interface LklOrderDrawService extends IBaseService<LklOrderDraw> {
* @param merOrderNo 商户订单号
* @return
*/
LklOrderDraw getByByMercIdAndMerOrderNo(String mercId, String merOrderNo);
LklOrderDraw getByMercIdAndMerOrderNo(String mercId, String merOrderNo);
/**

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.shop.lakala.service;
import com.suisung.mall.common.modules.lakala.LklReceiveNotifyLog;
import com.suisung.mall.core.web.service.IBaseService;
public interface LklReceiveNotifyLogService extends IBaseService<LklReceiveNotifyLog> {
/**
* 新增或更新拉卡拉确认收货通知日志记录
*
* @param lklReceiveNotifyJSON 拉卡拉通知的JSON数据
* @return 处理结果true表示成功false表示失败
*/
Boolean addOrUpdate(String lklReceiveNotifyJSON);
/**
* 根据订单号获取拉卡拉确认收货通知日志记录
*
* @param orderId 商户订单号
* @return 拉卡拉确认收货通知日志记录如果不存在则返回null
*/
LklReceiveNotifyLog getByOrderId(String orderId);
}

View File

@ -153,10 +153,10 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Lazy
@Resource
private ShopOrderBaseService shopOrderBaseService;
//
// @Lazy
// @Resource
// private ShopOrderInfoService shopOrderInfoService;
@Lazy
@Resource
private LklReceiveNotifyLogService lklReceiveNotifyLogService;
@Lazy
@Resource
@ -197,6 +197,8 @@ public class LakalaApiServiceImpl implements LakalaApiService {
/**
* 聚合扫码-交易查询
* 参考https://o.lakala.com/#/home/document/detail?id=116
* 说明查询交易中如果返回响应CODE为BBS00000仅表示查到了这笔交易交易本身的成功与否状态要查看响应报文中的trade_state这个值
*
* @param storeId
* @param orderId
@ -204,7 +206,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
*/
@Override
public JSONObject tradeQuery(Integer storeId, String orderId) {
if (ObjectUtil.isEmpty(storeId) || StrUtil.isBlank(orderId)) {
if (CheckUtil.isEmpty(storeId) || StrUtil.isBlank(orderId)) {
return null;
}
@ -228,13 +230,18 @@ public class LakalaApiServiceImpl implements LakalaApiService {
//3. 发送请求
String responseStr = LKLSDK.httpPost(v3LabsQueryTradequeryRequest);
if (StrUtil.isBlank(responseStr)) {
throw new ApiException(I18nUtil._("交易查询无响应!"));
return null;
}
JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
// || !lakalaRespJSON.getStr("code").equals("BBS00000")
if (lakalaRespJSON != null
&& StrUtil.isNotBlank(lakalaRespJSON.getStr("code"))
&& lakalaRespJSON.getStr("code").equals("BBS00000")
&& lakalaRespJSON.getJSONObject("resp_data") != null) {
return lakalaRespJSON.getJSONObject("resp_data");
}
return lakalaRespJSON;
return null;
} catch (SDKException e) {
log.error("交易查询失败:", e);
throw new ApiException(I18nUtil._("交易查询失败!"), e);
@ -781,7 +788,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
/**
* 发货类交易确认收货通知处理 微信通知拉卡拉拉卡拉通知我们系统已经完成确认收货
* 重要接口直接影响到分账执行的发货类交易确认收货通知处理 微信通知拉卡拉拉卡拉通知我们系统已经完成确认收货
* <p>
* 参考文档https://o.lakala.com/#/home/document/detail?id=1003
* </p>
@ -813,6 +820,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
return JSONUtil.createObj().set("code", "FAIL").set("message", "返回数据转换异常!");
}
// 不管成功与否写入确认收货通知日志后续补偿遗漏分账的材料
lklReceiveNotifyLogService.addOrUpdate(paramsJSON.toString());
// 订单是否为合单
boolean isCombine = paramsJSON.containsKey("out_split_rsp_infos");
log.debug("[确认收货通知] 检查是否为合单订单: isCombine={}", isCombine);
@ -825,9 +835,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
String tradeNo = paramsJSON.getStr("trade_no");
String tradeTime = paramsJSON.getStr("trade_time"); // 实际交易完成时间yyyyMMddHHmmss
// 直接截取前8位获取日期部分
String logDate = tradeTime != null && tradeTime.length() >= 8 ? tradeTime.substring(0, 8) : null;
String logDate = StrUtil.isNotBlank(tradeTime) && tradeTime.length() >= 8 ? tradeTime.substring(0, 8) : null;
if (logDate == null) {
logDate = DateUtil.format(new Date(), "yyyyMMdd"); // 当前时间
logDate = DateUtil.format(new Date(), "yyyyMMdd"); // 收到确认收货通知的日期当前日期
}
log.debug("[确认收货通知] 获取基础交易信息: logNo={} tradeNo={} logDate={}", logNo, tradeNo, logDate);
@ -844,6 +854,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
logNo, tradeState, merchantNo, originTradeNo, originLogNo);
if (isCombine) {
// 该分支要废弃支付时请不要走合单交易
log.debug("[确认收货通知] 处理合单订单,开始获取子单信息");
// 合单的时候获取子单信息
JSONObject goodsOrderInfo = CommonService.getLklCombineSplitRespInfo(merchantNo, paramsJSON.getStr("out_split_rsp_infos"), false);
@ -862,15 +873,9 @@ public class LakalaApiServiceImpl implements LakalaApiService {
log.warn("[确认收货通知] 未能获取到商品子单信息,使用原始参数进行处理");
}
} else {
// 非合单订单确认收货响应数据{"trade_no":"20251015110110000066202154232129","log_no":"66202154232129","trade_state":"SUCCESS",
// "total_amount":"2950","trade_time":"20251015165538",
// "complete_notify_url":"https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify",
// "merchant_no":"8226330541100H4","sub_mch_id":"812310610","origin_trade_no":"20251015110113130266250075936522",
// "origin_log_no":"66250075936522","origin_out_trade_no":"DD_20251015_2"}
log.debug("[确认收货通知] 从订单信息中获取原始交易号: originTradeNo={} originLogNo={}", originTradeNo, originLogNo);
}
if (StrUtil.isBlank(tradeState) || !"SUCCESS".equals(tradeState)) {
log.warn("[确认收货通知] 交易状态未成功,不做任何处理: tradeState={}", tradeState);
return JSONUtil.createObj().set("code", "FAIL").set("message", "交易状态未成功,不做任何处理!");
@ -882,6 +887,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
return JSONUtil.createObj().set("code", "FAIL").set("message", "关键编号返回空值!");
}
// 查询
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByLklMchNoAndSubTradeNoAndSubLogNo(merchantNo, originTradeNo, originLogNo);
if (shopOrderLkl == null) {
log.warn("[确认收货通知] 订单不存在: merchantNo={}, originTradeNo={}, originLogNo={}",
@ -904,9 +910,10 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
log.info("[确认收货通知] 订单信息更新成功: orderId={}", shopOrderLkl.getOrder_id());
// 重要准备发起分账指令
log.info("[确认收货通知] 开始发起分账指令: merchantNo={}, receiveTradeNo={}, logNo={}",
merchantNo, shopOrderLkl.getLkl_receive_trade_no(), logNo);
// 重要准备发起分账指令
Pair<Boolean, String> separateResult = innerDoOrderSeparateByMerchantAndLogNo(merchantNo, shopOrderLkl.getLkl_receive_trade_no(), shopOrderLkl.getLkl_receive_log_no(), logDate);
if (!separateResult.getFirst()) {
@ -2333,6 +2340,8 @@ public class LakalaApiServiceImpl implements LakalaApiService {
if (CheckUtil.isEmpty(refCanSeparateAmt)) {
log.warn("[分账操作] 拉卡拉可分账金额为空或为0将使用系统计算金额, orderId={}, merchantNo={}",
orderId, lklMerchantNo);
} else {
log.error("[分账操作] 注意:拉卡拉提供的可分账金额无效:{}", refCanSeparateAmt);
}
}
@ -2396,23 +2405,23 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
// 计算拉卡拉手续费商家分账金额平台和代理商的分账金额
Pair<Boolean, LklSeparateWithTotalAmountDTO> calcResult = calculateAndEvaluateSharingParams(
LklSeparateWithTotalAmountDTO.SeparateResult calcResult = calculateAndEvaluateSharingParams(
shopOrderLkl.getTotal_amt(),
shoppingFeeInner,
mchSplitRatio,
platformSplitRatio,
agent1stSplitRatio, agent2ndSplitRatio, refCanSeparateAmt);
if (calcResult == null || !calcResult.getFirst() || calcResult.getSecond() == null) {
if (calcResult == null || !calcResult.getIsSuccess() || calcResult.getData() == null) {
log.error("[分账操作] 分账参数评估失败,无法分账, orderId={}, merchantNo={}", orderId, merchantNo);
// 更新分账出错信息
shopOrderLkl.setSeparate_msg("分账数据评估,结果无法分账");
shopOrderLkl.setSeparate_msg(calcResult.getErrMsg());
shopOrderLklService.safeUpdate(shopOrderLkl);
return Pair.of(false, "分账数据评估,结果无法分账");
return Pair.of(false, calcResult.getErrMsg());
}
LklSeparateWithTotalAmountDTO lklSeparateDTO = calcResult.getSecond();
LklSeparateWithTotalAmountDTO lklSeparateDTO = calcResult.getData();
log.info("[分账操作] 分账计算完成, orderId={}, merchantNo={}, 分账总金额={}",
orderId, merchantNo, lklSeparateDTO.getCanSeparateAmount());
@ -2420,12 +2429,13 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 更新分账计算结果
shopOrderLkl.setSeparate_remark(lklSeparateDTO.toString()); // 写入分账具体情况
if (CheckUtil.isEmpty(refCanSeparateAmt)) {
// 如果拉卡拉参考可分账金额无效使用程序的计算结果的可分账金额
refCanSeparateAmt = lklSeparateDTO.getCanSeparateAmount();
}
shopOrderLkl.setSplit_amt_ref(refCanSeparateAmt);
// 可分账金额校验
if (CheckUtil.isEmpty(refCanSeparateAmt) || refCanSeparateAmt <= 0) {
if (CheckUtil.isEmpty(refCanSeparateAmt)) {
String errorMsg = String.format("[分账操作] 可分账金额低于1分钱跳过分账, orderId=%s, merchantNo=%s, amount=%d",
orderId, merchantNo, refCanSeparateAmt);
log.error(errorMsg);
@ -3699,7 +3709,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
* @return Pair<Boolean, LklSeparateDTO> 分账参数评估结果第一个元素表示是否成功第二个元素为分账参数对象
*/
@Override
public Pair<Boolean, LklSeparateWithTotalAmountDTO> calculateAndEvaluateSharingParams(
public LklSeparateWithTotalAmountDTO.SeparateResult calculateAndEvaluateSharingParams(
Integer orderPayAmount,
Integer shippingFeeInner,
BigDecimal mchSplitRatioRaw,
@ -3707,38 +3717,39 @@ public class LakalaApiServiceImpl implements LakalaApiService {
BigDecimal agent1stRatio,
BigDecimal agent2ndRatio,
Integer refCanSeparateAmt) {
log.debug("[分账参数计算] 开始计算分账参数: orderPayAmount={}, shippingFeeInner={}, " +
log.debug("[分账计算] 开始计算分账参数: orderPayAmount={}, shippingFeeInner={}, " +
"mchSplitRatioRaw={}, platSplitRatio={}, agent1stRatio={}, agent2ndRatio={}, refCanSeparateAmt={}",
orderPayAmount, shippingFeeInner, mchSplitRatioRaw, platSplitRatio,
agent1stRatio, agent2ndRatio, refCanSeparateAmt);
String errMsg = "";
// 参数校验
if (orderPayAmount == null || orderPayAmount <= 0) {
log.warn("[分账参数计算] 订单支付金额参数无效: orderPayAmount={}", orderPayAmount);
return Pair.of(false, null);
log.warn("[分账计算] 订单支付金额参数无效: orderPayAmount={}", orderPayAmount);
return LklSeparateWithTotalAmountDTO.SeparateResult.failure("订单支付金额参数无效");
}
if (mchSplitRatioRaw == null || mchSplitRatioRaw.compareTo(BigDecimal.ZERO) <= 0 || mchSplitRatioRaw.compareTo(BigDecimal.valueOf(100)) > 0) {
log.warn("[分账参数计算] 商户分账比例参数无效: mchSplitRatioRaw={}", mchSplitRatioRaw);
return Pair.of(false, null);
log.warn("[分账计算] 商户分账比例参数无效: mchSplitRatioRaw={}", mchSplitRatioRaw);
return LklSeparateWithTotalAmountDTO.SeparateResult.failure("订单支付金额参数无效");
}
// 计算商家分账比例转换为小数
BigDecimal mchSplitRatio = mchSplitRatioRaw.divide(new BigDecimal(100));
log.debug("[分账参数计算] 商家分账比例转换: {} -> {}", mchSplitRatioRaw, mchSplitRatio);
log.debug("[分账计算] 商家分账比例转换: {} -> {}", mchSplitRatioRaw, mchSplitRatio);
// 平台分账比例处理
BigDecimal platformSplitRatio = platSplitRatio;
if (platformSplitRatio == null || platformSplitRatio.compareTo(BigDecimal.ZERO) <= 0) {
platformSplitRatio = BigDecimal.valueOf(0.01); // 默认平台分账1%
log.debug("[分账参数计算] 使用默认平台分账比例: {}", platformSplitRatio);
log.debug("[分账计算] 使用默认平台分账比例: {}", platformSplitRatio);
}
// 内部配送费处理
Integer actualShippingFeeInner = CheckUtil.isEmpty(shippingFeeInner) ? 0 : shippingFeeInner;
BigDecimal wxFeeRatio = StrUtil.isEmpty(wxFee) ? BigDecimal.valueOf(0.0025) : new BigDecimal(wxFee).divide(BigDecimal.valueOf(100));
log.debug("[分账参数计算] 配送费: {}, 拉卡拉费率: {}", actualShippingFeeInner, wxFeeRatio);
log.debug("[分账计算] 配送费: {}, 拉卡拉费率: {}", actualShippingFeeInner, wxFeeRatio);
// 构建分账参数对象
LklSeparateWithTotalAmountDTO lklSeparateDTO = new LklSeparateWithTotalAmountDTO();
@ -3751,33 +3762,28 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 设置代理商分账比例
if (agent1stRatio != null && agent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
lklSeparateDTO.setAgent1stRatio(agent1stRatio);
log.debug("[分账参数计算] 设置一级代理商分账比例: {}", agent1stRatio);
log.debug("[分账计算] 设置一级代理商分账比例: {}", agent1stRatio);
}
if (agent2ndRatio != null && agent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
lklSeparateDTO.setAgent2ndRatio(agent2ndRatio);
log.debug("[分账参数计算] 设置二级代理商分账比例: {}", agent2ndRatio);
log.debug("[分账计算] 设置二级代理商分账比例: {}", agent2ndRatio);
}
// 设置参考可分账金额
if (refCanSeparateAmt != null && refCanSeparateAmt > 0) {
if (CheckUtil.isNotEmpty(refCanSeparateAmt)) {
lklSeparateDTO.setRefCanSeparateAmount(refCanSeparateAmt);
log.debug("[分账参数计算] 设置参考可分账金额: {}", refCanSeparateAmt);
log.debug("[分账计算] 拉卡拉的参考可分账金额: {}", refCanSeparateAmt);
} else {
log.error("[分账计算] 注意:拉卡拉的参考可分账金额无效:{}", refCanSeparateAmt);
}
try {
// 根据分账模式执行不同的分账计算
Pair<Boolean, LklSeparateWithTotalAmountDTO> canSeparateAmtResult = lklSeparateDTO.calculateSeparateAmount();
if (!canSeparateAmtResult.getFirst()) {
log.warn("[分账参数计算] 分账参数有误,分账估算失败");
return Pair.of(false, lklSeparateDTO);
}
log.info("[分账参数计算] 分账估算成功, result={}", lklSeparateDTO);
return Pair.of(true, lklSeparateDTO);
return lklSeparateDTO.calculateSeparateAmount();
} catch (Exception e) {
log.error("[分账参数计算] 分账参数有误,分账估算失败", e);
return Pair.of(false, lklSeparateDTO);
log.error("[分账计算] 分账参数有误,分账估算失败", e);
return LklSeparateWithTotalAmountDTO.SeparateResult.failure("分账参数有误,分账估算失败");
}
}

View File

@ -81,7 +81,7 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl<LklOrderDrawMapper,
* @return 拉卡拉订单分账记录未找到返回null
*/
@Override
public LklOrderDraw getByByMercIdAndMerOrderNo(String mercId, String merOrderNo) {
public LklOrderDraw getByMercIdAndMerOrderNo(String mercId, String merOrderNo) {
// 参数校验
if (StrUtil.isBlank(mercId) && StrUtil.isBlank(merOrderNo)) {
log.warn("[LklOrderDraw] 查询记录参数校验失败,商户号和商户订单号均为空");

View File

@ -0,0 +1,107 @@
package com.suisung.mall.shop.lakala.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.modules.lakala.LklReceiveNotifyLog;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.lakala.mapper.LklReceiveNotifyLogMapper;
import com.suisung.mall.shop.lakala.service.LklReceiveNotifyLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class LklReceiveNotifyLogServiceImpl extends BaseServiceImpl<LklReceiveNotifyLogMapper, LklReceiveNotifyLog> implements LklReceiveNotifyLogService {
/**
* 新增或更新拉卡拉确认收货通知日志记录
*
* @param lklReceiveNotifyJSON 拉卡拉确认收货通知的JSON数据
* @return 操作结果成功返回true失败返回false
*/
@Override
public Boolean addOrUpdate(String lklReceiveNotifyJSON) {
// 参数校验
if (StrUtil.isBlank(lklReceiveNotifyJSON)) {
log.warn("[LklReceiveNotifyLog] 新增或更新记录参数校验失败,通知数据为空");
return false;
}
try {
JSONObject respJson = JSONUtil.parseObj(lklReceiveNotifyJSON);
if (ObjectUtil.isEmpty(respJson)) {
log.warn("[LklReceiveNotifyLog] 新增或更新记录参数校验失败,拉卡拉返回数据为空");
return false;
}
String orderId = respJson.getStr("origin_out_trade_no");
if (StrUtil.isBlank(orderId)) {
log.warn("[LklReceiveNotifyLog] 新增或更新记录参数校验失败,订单号为空");
return false;
}
// 直接构建记录对象
LklReceiveNotifyLog record = new LklReceiveNotifyLog();
record.setOrderId(orderId);
record.setRespJson(lklReceiveNotifyJSON);
// 使用 saveOrUpdate 方法替代先查询再判断的方式
boolean result = saveOrUpdate(record,
new QueryWrapper<LklReceiveNotifyLog>().eq("order_id", orderId));
if (result) {
log.debug("[LklReceiveNotifyLog] 记录保存成功,订单号={}", orderId);
} else {
log.error("[LklReceiveNotifyLog] 记录保存失败,订单号={}", orderId);
}
return result;
} catch (Exception e) {
log.error("[LklReceiveNotifyLog] 处理拉卡拉确认收货通知记录异常,通知数据={}",
lklReceiveNotifyJSON, e);
return false;
}
}
/**
* 根据订单号获取拉卡拉确认收货通知日志记录
*
* @param orderId 订单号
* @return 拉卡拉确认收货通知日志记录未找到返回null
*/
@Override
public LklReceiveNotifyLog getByOrderId(String orderId) {
// 参数校验
if (StrUtil.isBlank(orderId)) {
log.warn("[LklReceiveNotifyLog] 查询记录参数校验失败,订单号为空");
return null;
}
try {
log.debug("[LklReceiveNotifyLog] 开始查询拉卡拉确认收货通知记录,订单号={}", orderId);
QueryWrapper<LklReceiveNotifyLog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_id", orderId);
LklReceiveNotifyLog result = getOne(queryWrapper);
if (result != null) {
log.debug("[LklReceiveNotifyLog] 查询到拉卡拉确认收货通知记录ID={}", result.getId());
} else {
log.debug("[LklReceiveNotifyLog] 未查询到拉卡拉确认收货通知记录");
}
return result;
} catch (Exception e) {
log.error("[LklReceiveNotifyLog] 查询拉卡拉确认收货通知记录异常,订单号={}", orderId, e);
return null;
}
}
}

View File

@ -324,7 +324,7 @@ public class UserOrderController extends BaseControllerImpl {
@ApiOperation(value = "可预约订单的时间槽", notes = "结算中心页面,可预约订单的时间槽")
@RequestMapping(value = "/booking_time_args", method = RequestMethod.GET)
public CommonResult listInvoice(@RequestParam(name = "store_ids", defaultValue = "") String store_ids) {
public CommonResult genBookingOrderArgList(@RequestParam(name = "store_ids", defaultValue = "") String store_ids) {
List<BookingArgDTO> list = shopOrderInfoService.genBookingOrderArgList(store_ids);
return CommonResult.success(list);
}

View File

@ -616,4 +616,6 @@ public interface ShopOrderBaseService extends IBaseService<ShopOrderBase> {
* @return
*/
Boolean updateOrderTime(String orderId, Date orderTime);
}

View File

@ -5323,7 +5323,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
ShopProductItem shopProductItem = shopProductItemService.get(item_id);
if (shopProductItem != null) {
logger.info("无法获取订单中的商品,请检查!");//商品存在才执行库存扣减
// throw new ApiException(I18nUtil._("无法获取订单中的商品,请检查!"));
// throw new ApiException(I18nUtil._("无法获取订单中的商品,请检查!"));
Integer item_quantity_frozen = shopProductItem.getItem_quantity_frozen();
int quantity_frozen = item_quantity_frozen - order_item_quantity;
shopProductItem.setItem_quantity_frozen(quantity_frozen > 0 ? quantity_frozen : 0);
@ -6325,9 +6325,9 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
throw new ApiException(I18nUtil._("店铺关闭中,不可以下单!"));
}
// 判断店铺是否打烊打烊不能下单
// 判断店铺是否已歇业不能下单
Pair<Integer, String> storeBizState = shopStoreBaseService.getStoreBizState(currStoreId);
if (storeBizState != null && CommonConstant.Disable2.equals(storeBizState.getFirst())) {
if (storeBizState != null && CommonConstant.Store_Biz_State_Closed.equals(storeBizState.getFirst())) {
throw new ApiException(I18nUtil._(storeBizState.getSecond() + ",无法提交订单。"));
}
@ -7784,7 +7784,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
BigDecimal storeSplitRatio = shopStoreBaseService.getStoreSplitRatio(store_id, false);
// 计算平台和代理商的分账金额
Pair<Boolean, LklSeparateWithTotalAmountDTO> calcResult = lakalaApiService.calculateAndEvaluateSharingParams(
LklSeparateWithTotalAmountDTO.SeparateResult calcResult = lakalaApiService.calculateAndEvaluateSharingParams(
Convert.toInt(order_payment_amount.multiply(BigDecimal.valueOf(100))),
innerMinDeliverFee,
storeSplitRatio,
@ -7792,9 +7792,9 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
null, null, null);
// 计算平台费
if (calcResult != null && calcResult.getFirst() && calcResult.getSecond() != null) {
if (calcResult != null && calcResult.getIsSuccess() && calcResult.getData() != null) {
try {
LklSeparateWithTotalAmountDTO lklSeparateDTO = calcResult.getSecond();
LklSeparateWithTotalAmountDTO lklSeparateDTO = calcResult.getData();
// 确保分账金额不为负数
BigDecimal totalSeparateAmount = BigDecimal.valueOf(lklSeparateDTO.getCanSeparateAmount())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
@ -9365,4 +9365,5 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
}
}

View File

@ -26,6 +26,7 @@ import com.suisung.mall.common.modules.order.ShopOrderStateLog;
import com.suisung.mall.common.modules.pay.PayPlantformResource;
import com.suisung.mall.common.modules.plantform.ShopPlantformFeedback;
import com.suisung.mall.common.modules.product.ShopProductComment;
import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.modules.store.ShopStoreInfo;
import com.suisung.mall.common.pojo.dto.BookingArgDTO;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
@ -59,6 +60,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
@ -852,84 +854,6 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
return this.count(queryWrapper);
}
/**
* 检查订单预约参数是否合法
*
* @param storeId 店铺ID
* @param bookingState 预约配送状态
* @param bookingBeginTimeStr 预约开始时间
* @param bookingEndTimeStr 预约截止时间
* @return 验证结果Pair第一个元素表示是否通过验证第二个元素为提示信息
*/
@Override
public Pair<Boolean, String> checkBookingOrderArgs(Integer storeId, Integer bookingState, String bookingBeginTimeStr, String bookingEndTimeStr) {
// 1. 必填参数检查
if (CheckUtil.isEmpty(storeId) || CheckUtil.isEmpty(bookingState)) {
return Pair.of(false, "[预约单校验] 缺少必要参数");
}
// 2. 预约状态检查
if (!CommonConstant.Order_Booking_State_YY.equals(bookingState)) {
return Pair.of(true, "[预约单校验] 非预订单,跳过验证");
}
// 3. 时间格式检查
if (StrUtil.isBlank(bookingBeginTimeStr)) {
return Pair.of(false, "[预约单校验] 预约时间不能为空");
}
Date bookingBeginTime = DateTimeUtils.tryParseDateTimeToDate(bookingBeginTimeStr);
Date bookingEndTime = DateTimeUtils.tryParseDateTimeToDate(bookingEndTimeStr);
if (bookingBeginTime == null) {
return Pair.of(false, "[预约单校验] 预约时间格式有误");
}
// 4. 时间逻辑检查 - 开始时间不能晚于结束时间
if (bookingEndTime != null && bookingBeginTime.after(bookingEndTime)) {
return Pair.of(false, "[预约单校验] 开始时间不能晚于截止时间");
}
// 5. 店铺信息检查
ShopStoreInfo shopStoreInfo = shopStoreInfoService.getShopStoreInfoByStoreId(storeId);
if (shopStoreInfo == null) {
return Pair.of(false, "[预约单校验] 店铺信息有误");
}
if (StrUtil.isBlank(shopStoreInfo.getStore_opening_hours()) || StrUtil.isBlank(shopStoreInfo.getStore_close_hours())) {
shopStoreInfo.setStore_opening_hours("09:00");
shopStoreInfo.setStore_close_hours("06:00");
logger.warn("[预约单校验] 店铺营业时间未设置,请联系商家,默认指定 {}-{}", shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours());
}
// 6. 店铺营业时间检查
if (!DateTimeUtils.isTimeInRange(shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours(), bookingBeginTime)) {
String message = StrUtil.format("[预约单校验] 请在 {}-{} 店铺营业时间内预约下单", shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours());
return Pair.of(false, message);
}
// 7. 预约时间范围检查 - 仅能预约50分钟后的订单防止用户在下单页面停留太长45分钟也是可以通过的
Date fortyFiveMinutesLater = DateUtil.offsetMinute(new Date(), CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER - 5);
if (bookingBeginTime.before(fortyFiveMinutesLater)) {
return Pair.of(false, String.format("[预约单校验] 请预约%d分后的订单", CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER));
}
return Pair.of(true, "[预约单校验] 成功");
}
/**
* 判断订单是否为预约订单
*
* @param orderInfo 订单信息
* @return 是否为预约订单
*/
@Override
public Boolean isBookingOrder(ShopOrderInfo orderInfo) {
return orderInfo != null
&& CommonConstant.Order_Booking_State_YY.equals(orderInfo.getBooking_state())
&& !CheckUtil.isEmpty(orderInfo.getBooking_at())
&& orderInfo.getBooking_at().longValue() >= System.currentTimeMillis() / 1000;
}
/**
* 执行预约订单到点后向顺丰同城订单任务
*
@ -1008,6 +932,94 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
}
}
/**
* 检查订单预约参数是否合法
*
* @param storeId 店铺ID
* @param bookingState 预约配送状态
* @param bookingBeginTimeStr 预约开始时间
* @param bookingEndTimeStr 预约截止时间
* @return 验证结果Pair第一个元素表示是否通过验证第二个元素为提示信息
*/
@Override
public Pair<Boolean, String> checkBookingOrderArgs(Integer storeId, Integer bookingState, String bookingBeginTimeStr, String bookingEndTimeStr) {
// 1. 必填参数检查
if (CheckUtil.isEmpty(storeId) || CheckUtil.isEmpty(bookingState)) {
return Pair.of(false, "[预约单校验] 缺少必要参数");
}
// 2. 预约状态检查
if (!CommonConstant.Order_Booking_State_YY.equals(bookingState)) {
return Pair.of(true, "[预约单校验] 非预订单,跳过验证");
}
// 3. 时间格式检查
if (StrUtil.isBlank(bookingBeginTimeStr)) {
return Pair.of(false, "[预约单校验] 预约时间不能为空");
}
Date bookingBeginTime = DateTimeUtils.tryParseDateTimeToDate(bookingBeginTimeStr);
Date bookingEndTime = DateTimeUtils.tryParseDateTimeToDate(bookingEndTimeStr);
if (bookingBeginTime == null) {
return Pair.of(false, "[预约单校验] 预约时间格式有误");
}
// 4. 时间逻辑检查 - 开始时间不能晚于结束时间
if (bookingEndTime != null && bookingBeginTime.after(bookingEndTime)) {
return Pair.of(false, "[预约单校验] 开始时间不能晚于截止时间");
}
ShopStoreBase shopStoreBase = shopStoreBaseService.getById(storeId);
// 5. 店铺信息检查
ShopStoreInfo shopStoreInfo = shopStoreInfoService.getShopStoreInfoByStoreId(storeId);
if (shopStoreBase == null || shopStoreInfo == null) {
return Pair.of(false, "[预约单校验] 店铺信息有误");
}
if (CheckUtil.isEmpty(shopStoreBase.getStore_is_open())
|| CommonConstant.Store_Biz_State_Closed.equals(shopStoreBase.getStore_biz_state())) {
return Pair.of(false, "[预约单校验] 店铺已歇业,无法下单");
}
if (StrUtil.isBlank(shopStoreInfo.getStore_opening_hours()) || StrUtil.isBlank(shopStoreInfo.getStore_close_hours())) {
// shopStoreInfo.setStore_opening_hours("00:00");
// shopStoreInfo.setStore_close_hours("23:59");
// logger.warn("[预约单校验] 店铺营业时间未设置,请联系商家,默认指定 {}-{}", shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours());
logger.warn("[预约单校验] 店铺营业时间未设置,请联系商家{}", shopStoreInfo.getStore_id());
return Pair.of(false, "[预约单校验] 店铺营业时间未设置,请联系商家");
}
// 6. 店铺营业时间检查
if (DateTimeUtils.isTimeInRange(shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours(), bookingBeginTime) != 0) {
String message = StrUtil.format("[预约单校验] 请在 {}-{} 营业时间内预约下单", shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours());
return Pair.of(false, message);
}
// 7. 预约时间范围检查 - 仅能预约50分钟后的订单防止用户在下单页面停留太长45分钟也是可以通过的
Date fortyFiveMinutesLater = DateUtil.offsetMinute(new Date(), CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER - 5);
if (bookingBeginTime.before(fortyFiveMinutesLater)) {
return Pair.of(false, String.format("[预约单校验] 请预约%d分钟后下单", CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER));
}
return Pair.of(true, "[预约单校验] 成功");
}
/**
* 判断订单是否为预约订单
*
* @param orderInfo 订单信息
* @return 是否为预约订单
*/
@Override
public Boolean isBookingOrder(ShopOrderInfo orderInfo) {
return orderInfo != null
&& CommonConstant.Order_Booking_State_YY.equals(orderInfo.getBooking_state())
&& !CheckUtil.isEmpty(orderInfo.getBooking_at())
&& orderInfo.getBooking_at().longValue() >= System.currentTimeMillis() / 1000;
}
/**
* 根据店铺的营业时间范围生成可预约下单的参数
*
@ -1016,30 +1028,70 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
*/
@Override
public List<BookingArgDTO> genBookingOrderArgList(String storeIds) {
// 初始化默认营业时间
Map<String, String> timesMap = new HashMap<>();
// 初始化营业时间对象
Pair<String, String> storeBizTimeRange = null;
// 如果storeId不为空则尝试获取店铺信息
if (StrUtil.isNotBlank(storeIds)) {
List<Map<String, String>> timesMapList = selStoreBizTimeMapList(storeIds);
if (!CollUtil.isEmpty(timesMapList)) {
timesMap = DateTimeUtils.findTimeInterSection(timesMapList);
// 根据一个或多个店铺id获取有效店铺的有效营业时间段
List<Pair<String, String>> storeBizTimeRangesList = selectMulStoreBizTimeRanges(storeIds);
if (!CollUtil.isEmpty(storeBizTimeRangesList)) {
// 获取多个店铺的营业时间段的一个交集
storeBizTimeRange = DateTimeUtils.findTimeInterSection(storeBizTimeRangesList);
}
}
List<BookingArgDTO> result = new ArrayList<>();
if (storeBizTimeRange == null
|| StrUtil.isBlank(storeBizTimeRange.getFirst())
|| StrUtil.isBlank(storeBizTimeRange.getSecond())) {
// 没有具体的营业时间段
logger.info("[生成预约参数] 未找到店铺相关营业时间");
return Collections.emptyList();
}
// 生成未来7天的数据
Date now = new Date();
for (int i = 0; i < 7; i++) {
Date currentDate = DateUtil.offsetDay(now, i);
// 显示几天的时间槽默认7天
int daysCnt = 7;
List<BookingArgDTO> bookingArgList = new ArrayList<>();
Date startDate = new Date();
//-1-在时间段之前 0-时间段内1-在时间段之后
int inRangeVal = 0;
// 今天是否可预约在营业时间之前或之中
boolean isTodayAvailable = true;
// 获取开业活动筹备中店铺最晚的日期 yyyy-MM-dd, 如果存在说明几个店铺中有开业活动筹备中的店铺
Date latestBizOpeningDate = shopStoreBaseService.getLatestBizOpeningDate(storeIds);
if (latestBizOpeningDate != null) {
if (latestBizOpeningDate.after(startDate)) {
// 如果开业筹备中的日期在今天之后预约以这个开业日期为起始日期
startDate = latestBizOpeningDate;
inRangeVal = 1; // 今天不在营业时间段内
isTodayAvailable = false; // 今天不能预约
} else {
// 如果开业日期已过期把这个日期设置为今天的日期
inRangeVal = 0;
isTodayAvailable = true;
}
} else {
// 判断今天还能不能立即下单和预约下单就有今天的时间槽不能就没有今天的时间槽
//-1-在时间段之前 0-时间段内1-在时间段之后
inRangeVal = DateTimeUtils.isCurrentTimeInRange(storeBizTimeRange.getFirst(), storeBizTimeRange.getSecond());
// 确定起始日期
startDate = inRangeVal == 1 ? DateUtil.offsetDay(startDate, 1) : startDate;
isTodayAvailable = inRangeVal <= 0; // 今天是否可预约在营业时间之前或之中
}
// 生成预约日期参数
for (int i = 0; i < daysCnt; i++) {
Date currentDate = DateUtil.offsetDay(startDate, i);
BookingArgDTO bookingArgDTO = new BookingArgDTO();
// 设置日期相关信息
String dateStr = DateUtil.format(currentDate, "yyyy-MM-dd");
String displayDateStr = DateUtil.format(currentDate, "MM月dd日");
// 安全获取星期信息
String weekStr = "星期";
try {
@ -1049,184 +1101,148 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
}
// 设置date_title
if (i == 0) {
bookingArgDTO.setDate_title("今天(" + weekStr + "");
} else if (i == 1) {
bookingArgDTO.setDate_title("明天(" + weekStr + "");
String dateTitle;
if (i == 0 && latestBizOpeningDate == null) {
dateTitle = isTodayAvailable ? "今天" : "明天";
} else if (i == 1 && latestBizOpeningDate == null) {
dateTitle = isTodayAvailable ? "明天" : "后天";
} else {
bookingArgDTO.setDate_title(displayDateStr + "" + weekStr + "");
dateTitle = displayDateStr;
}
bookingArgDTO.setDate_title(dateTitle + "" + weekStr + "");
bookingArgDTO.setDate_str(displayDateStr);
bookingArgDTO.setDate(dateStr);
// 生成时间项
String startTimeStr = storeBizTimeRange.getFirst();
String endTimeStr = storeBizTimeRange.getSecond();
bookingArgDTO.setWorking_hours(String.format("%s-%s", startTimeStr, endTimeStr));
// 生成时间槽
List<BookingArgDTO.BookingArgItem> items = new ArrayList<>();
// 如果是今天始终添加"立即送出"选项
if (i == 0) {
BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
immediateItem.setTime_title("立即送出");
immediateItem.setBooking_at(0L);
immediateItem.setBooking_state(1);
immediateItem.setBooking_begin_time("");
immediateItem.setBooking_end_time("");
items.add(immediateItem);
// 参数校验
if (StrUtil.isBlank(dateStr) || StrUtil.isBlank(startTimeStr) || StrUtil.isBlank(endTimeStr)) {
bookingArgDTO.setItems(items);
bookingArgList.add(bookingArgDTO);
continue;
}
// 只有当 timesMap 不为空时才生成其他时间槽
if (!ObjectUtil.isEmpty(timesMap) && StrUtil.isNotBlank(timesMap.get("startTimeStr")) && StrUtil.isNotBlank(timesMap.get("endTimeStr"))) {
String startTimeStr = timesMap.get("startTimeStr");
String endTimeStr = timesMap.get("endTimeStr");
List<BookingArgDTO.BookingArgItem> timeSlots = generateTimeSlots(dateStr, startTimeStr, endTimeStr, i == 0);
if (i == 0) {
// 对于今天移除除"立即送出"外的所有时间槽
items.addAll(timeSlots.stream().filter(item -> item.getBooking_state() != 1).collect(Collectors.toList()));
} else {
// 对于其他日期添加所有时间槽
items.addAll(timeSlots);
try {
// 解析营业时间
Date openTime = DateUtil.parse(dateStr + " " + startTimeStr + ":00", "yyyy-MM-dd HH:mm:ss");
Date closeTime = DateUtil.parse(dateStr + " " + endTimeStr + ":00", "yyyy-MM-dd HH:mm:ss");
// 验证营业时间有效性
if (openTime.after(closeTime)) {
logger.warn("[生成时间槽] 营业时间无效openTime: {}, closeTime: {}", openTime, closeTime);
bookingArgDTO.setItems(items);
bookingArgList.add(bookingArgDTO);
continue;
}
bookingArgDTO.setWorking_hours(String.format("%s-%s", startTimeStr, endTimeStr));
} else if (i == 0) {
// 如果timesMap为空今天只保留"立即送出"选项
logger.debug("[生成预约参数] timesMap为空今天只生成立即送出选项");
} else {
// 如果timesMap为空其他日期不生成任何时间槽
logger.debug("[生成预约参数] timesMap为空不生成{}的预约时间槽", dateStr);
continue; // 跳过当前日期
// 在营业时间段内且是今天添加"立即送出"选项
if (i == 0 && isTodayAvailable && inRangeVal == 0) {
BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
immediateItem.setTime_title("立即送出");
immediateItem.setBooking_at(0L);
immediateItem.setBooking_state(1);
immediateItem.setBooking_begin_time("");
immediateItem.setBooking_end_time("");
items.add(immediateItem);
}
// 确定时间槽开始时间
Date startTime = openTime;
if (i == 0 && isTodayAvailable && inRangeVal <= 0) {
// 当前时间+50分钟作为时间槽的开始时间
Date nowPlusFifty = DateUtil.offsetMinute(new Date(), CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER);
// 确保开始时间在营业时间范围内
if (nowPlusFifty.after(openTime)) {
if (nowPlusFifty.before(closeTime)) {
// 如果当前时间+50分钟在营业时间范围内则从该时间开始
startTime = nowPlusFifty;
logger.debug("[生成时间槽] 当前时间+50分钟在营业时间范围内则从该时间开始");
} else {
// 如果当前时间+50分钟已经超过营业结束时间则不生成后续时间槽
logger.debug("[生成时间槽] 当前时间+50分钟已超过营业结束时间不生成后续时间槽");
startTime = null; // 标记为不生成时间槽
}
}
}
// 生成时间槽
if (startTime != null) {
Date currentTimeSlot = startTime;
int slotCount = 0;
final int MAX_SLOTS = 96; // 最多生成96个时间段防止异常循环
while (currentTimeSlot.before(closeTime) && slotCount < MAX_SLOTS) {
Date endTimeSlot = DateUtil.offsetMinute(currentTimeSlot, 15);
// 如果结束时间超过了营业结束时间则使用营业结束时间
if (endTimeSlot.after(closeTime)) {
endTimeSlot = closeTime;
}
// 创建时间段项
BookingArgDTO.BookingArgItem timeItem = new BookingArgDTO.BookingArgItem();
String beginTimeStr = DateUtil.format(currentTimeSlot, "HH:mm");
String endTimeStrItem = DateUtil.format(endTimeSlot, "HH:mm");
timeItem.setTime_title(beginTimeStr + "-" + endTimeStrItem);
// 时间戳计算
long bookingAt = currentTimeSlot.getTime() / 1000;
timeItem.setBooking_at(bookingAt);
timeItem.setBooking_state(CommonConstant.Order_Booking_State_YY);
timeItem.setBooking_begin_time(DateUtil.format(currentTimeSlot, "yyyy-MM-dd HH:mm:ss"));
timeItem.setBooking_end_time(DateUtil.format(endTimeSlot, "yyyy-MM-dd HH:mm:ss"));
items.add(timeItem);
// 移动到下一个时间段
currentTimeSlot = endTimeSlot;
slotCount++;
// 防止时间相同导致的死循环
if (currentTimeSlot.equals(startTime)) {
logger.warn("[生成时间槽] 检测到时间循环,跳出循环");
break;
}
}
if (slotCount >= MAX_SLOTS) {
logger.warn("[生成时间槽] 时间段数量超过最大限制date: {}", dateStr);
}
}
} catch (Exception e) {
logger.error("[生成时间槽] 生成时间槽异常date: {}, openingHours: {}, closeHours: {}", dateStr, startTimeStr, endTimeStr, e);
}
bookingArgDTO.setItems(items);
result.add(bookingArgDTO);
bookingArgList.add(bookingArgDTO);
}
logger.debug("[生成预约参数] 成功生成预约参数storeId: {}, timesMap: {}, 参数数量: {}",
storeIds, timesMap, result.size());
return result;
storeIds, storeBizTimeRange, bookingArgList.size());
return bookingArgList;
}
/**
* 生成时间槽列表
*
* @param dateStr 日期字符串 yyyy-MM-dd
* @param openingHours 开始营业时间 HH:mm
* @param closeHours 结束营业时间 HH:mm
* @param isToday 是否是今天
* @return 时间槽列表
*/
private List<BookingArgDTO.BookingArgItem> generateTimeSlots(String dateStr, String openingHours, String closeHours, boolean isToday) {
// 参数校验
if (StrUtil.isBlank(dateStr) || StrUtil.isBlank(openingHours) || StrUtil.isBlank(closeHours)) {
logger.warn("[生成时间槽] 参数为空dateStr: {}, openingHours: {}, closeHours: {}", dateStr, openingHours, closeHours);
return Collections.emptyList();
}
try {
List<BookingArgDTO.BookingArgItem> items = new ArrayList<>();
// 添加"立即送出"选项仅限今天
if (isToday) {
BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
immediateItem.setTime_title("立即送出");
immediateItem.setBooking_at(0L);
immediateItem.setBooking_state(1);
immediateItem.setBooking_begin_time("");
immediateItem.setBooking_end_time("");
items.add(immediateItem);
}
// 解析营业时间
Date openTime = DateUtil.parse(dateStr + " " + openingHours + ":00", "yyyy-MM-dd HH:mm:ss");
Date closeTime = DateUtil.parse(dateStr + " " + closeHours + ":00", "yyyy-MM-dd HH:mm:ss");
// 验证营业时间有效性
if (openTime.after(closeTime)) {
logger.warn("[生成时间槽] 营业时间无效openTime: {}, closeTime: {}", openTime, closeTime);
return items;
}
// 时间槽间隔分钟
int slotInterval = 15;
// 对于今天需要特殊处理第二个时间槽从当前时间+50分钟开始
Date startTime = openTime;
if (isToday) {
// 当前时间+50分钟作为第二个时间槽的开始时间
Date nowPlusFifty = DateUtil.offsetMinute(new Date(), CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER);
// 如果当前时间+50分钟在营业时间范围内则从该时间开始
if (nowPlusFifty.after(openTime) && nowPlusFifty.before(closeTime)) {
startTime = nowPlusFifty;
}
// 如果当前时间+50分钟已经超过营业结束时间则不生成后续时间槽
else if (nowPlusFifty.after(closeTime)) {
logger.debug("[生成时间槽] 当前时间+50分钟已超过营业结束时间不生成后续时间槽");
return items;
}
}
// 生成时间槽
Date currentTimeSlot = startTime;
int slotCount = 0;
final int MAX_SLOTS = 48; // 最多生成48个时间段防止异常循环
while (currentTimeSlot.before(closeTime) && slotCount < MAX_SLOTS) {
Date endTimeSlot = DateUtil.offsetMinute(currentTimeSlot, slotInterval);
// 如果结束时间超过了营业结束时间则使用营业结束时间
if (endTimeSlot.after(closeTime)) {
endTimeSlot = closeTime;
}
// 创建时间段项
BookingArgDTO.BookingArgItem timeItem = new BookingArgDTO.BookingArgItem();
String beginTimeStr = DateUtil.format(currentTimeSlot, "HH:mm");
String endTimeStr = DateUtil.format(endTimeSlot, "HH:mm");
timeItem.setTime_title(beginTimeStr + "-" + endTimeStr);
// 时间戳计算
long bookingAt = currentTimeSlot.getTime() / 1000;
timeItem.setBooking_at(bookingAt);
timeItem.setBooking_state(2);
timeItem.setBooking_begin_time(DateUtil.format(currentTimeSlot, "yyyy-MM-dd HH:mm:ss"));
timeItem.setBooking_end_time(DateUtil.format(endTimeSlot, "yyyy-MM-dd HH:mm:ss"));
items.add(timeItem);
// 移动到下一个时间段
currentTimeSlot = endTimeSlot;
slotCount++;
// 防止时间相同导致的死循环
if (currentTimeSlot.equals(startTime)) {
logger.warn("[生成时间槽] 检测到时间循环,跳出循环");
break;
}
}
if (slotCount >= MAX_SLOTS) {
logger.warn("[生成时间槽] 时间段数量超过最大限制date: {}", dateStr);
}
logger.debug("[生成时间槽] 成功生成时间槽date: {}, 时间段数量: {}", dateStr, items.size());
return items;
} catch (Exception e) {
logger.error("[生成时间槽] 生成时间槽异常date: {}, openingHours: {}, closeHours: {}", dateStr, openingHours, closeHours, e);
return Collections.emptyList();
}
}
/**
* 根据 storeIds一个或多个 storeid 34,23,43,23,先对id去重再获取多个店铺的营业时间 List<map{startTimeStr, endTimeStr}> 列表list
* 根据一个或多个店铺id获取有效店铺的有效营业时间段
* <p>
* 根据 storeIds一个或多个 storeid 34,23,43,23,先对id去重
* 再获取多个店铺的营业时间 List<map{startTimeStr, endTimeStr}> 列表list
* 启动如果遇到某个店铺是开业活动筹备中的获取他的开始营业的的日期和营业时间段组合出特有营业时间段
*
* @param storeIds 以逗号分隔的店铺ID字符串
* @return 包含店铺营业时间信息的列表每个元素为包含opening_hours和close_hours的Map
*/
private List<Map<String, String>> selStoreBizTimeMapList(String storeIds) {
private List<Pair<String, String>> selectMulStoreBizTimeRanges(String storeIds) {
// 参数校验
if (StrUtil.isBlank(storeIds)) {
return Collections.emptyList();
@ -1246,10 +1262,21 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
return Collections.emptyList();
}
// 3. 批量获取店铺信息
// 3. 先获取符合条件的 store_id 列表,关联shop_store_base表要求store_is_open=1开启且store_biz_state!=2非关闭状态
QueryWrapper<ShopStoreBase> storeBaseQueryWrapper = new QueryWrapper<>();
storeBaseQueryWrapper.select("store_id")
.eq("store_is_open", CommonConstant.Enable)
.ne("store_biz_state", CommonConstant.Store_Biz_State_Closed)
.in("store_id", uniqueStoreIds);
List<Serializable> validStoreIds = shopStoreBaseService.findKey(storeBaseQueryWrapper);
if (validStoreIds.isEmpty()) {
return Collections.emptyList();
}
QueryWrapper<ShopStoreInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.select("store_opening_hours", "store_close_hours"); // 只查询必要字段
queryWrapper.in("store_id", storeIds);
queryWrapper.in("store_id", validStoreIds);
List<ShopStoreInfo> shopStoreInfos = shopStoreInfoService.find(queryWrapper);
@ -1257,9 +1284,7 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
return shopStoreInfos.stream()
.filter(Objects::nonNull)
.map(storeInfo -> {
Map<String, String> timeSlot = new HashMap<>();
timeSlot.put("startTimeStr", storeInfo.getStore_opening_hours());
timeSlot.put("endTimeStr", storeInfo.getStore_close_hours());
Pair<String, String> timeSlot = Pair.of(storeInfo.getStore_opening_hours(), storeInfo.getStore_close_hours());
return timeSlot;
})
.collect(Collectors.toList());

View File

@ -478,13 +478,13 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
BigDecimal item_unit_points = Convert.toBigDecimal(productItemMap.get("item_unit_points"));
BigDecimal item_unit_price = Convert.toBigDecimal(productItemMap.get("item_unit_price"));
Integer item_quantity = Convert.toInt(productItemMap.get("item_quantity"));
String is_open_automatic=Convert.toStr(productItemMap.get("is_open_automatic"),DicEnum.YESORNO_0.getCode());
if(is_open_automatic.equals(DicEnum.YESORNO_1.getCode())){
Integer automatic=Convert.toInt(productItemMap.get("automatic"),0);
if(automatic<item_quantity){
String is_open_automatic = Convert.toStr(productItemMap.get("is_open_automatic"), DicEnum.YESORNO_0.getCode());
if (is_open_automatic.equals(DicEnum.YESORNO_1.getCode())) {
Integer automatic = Convert.toInt(productItemMap.get("automatic"), 0);
if (automatic < item_quantity) {
throw new ApiException("次日补全不能小于库存");
}
if(automatic<1){
if (automatic < 1) {
throw new ApiException("次日补全必须大于0");
}
item.setAutomatic(automatic);
@ -737,9 +737,9 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
String product_image_old = "";
if (productId == null) {
// 生成商品ID:product_id
List<Long> productIds= shopNumberSeqService.batchCreateNextNo("product_id",1);
productId=productIds.get(0);
// productId = shopNumberSeqService.createNextNo("product_id");
List<Long> productIds = shopNumberSeqService.batchCreateNextNo("product_id", 1);
productId = productIds.get(0);
// productId = shopNumberSeqService.createNextNo("product_id");
if (null == productId) {
return Pair.of(false, I18nUtil._("生成商品编号异常!"));
@ -1035,9 +1035,9 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
field_row.setProduct_item_seq_id(product_item_seq_id);
//这儿应该提前生成编号使用
List<Long> itemIds= shopNumberSeqService.batchCreateNextNo("item_id",1);
item_id=itemIds.get(0);
// item_id = shopNumberSeqService.createNextNo("item_id");
List<Long> itemIds = shopNumberSeqService.batchCreateNextNo("item_id", 1);
item_id = itemIds.get(0);
// item_id = shopNumberSeqService.createNextNo("item_id");
if (null == item_id) {
return Pair.of(false, I18nUtil._("生成商品 ItemId 异常!"));
@ -1214,7 +1214,7 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
boolean $flag = false;
List $rs_row = new ArrayList();
ShopStoreBase $store_row = shopStoreBaseService.get(store_id);
List<Long> productIds= shopNumberSeqService.batchCreateNextNo("product_id",1);
List<Long> productIds = shopNumberSeqService.batchCreateNextNo("product_id", 1);
Long $product_id = productIds.get(0);
//Long $product_id = shopNumberSeqService.createNextNo("product_id");
@ -1389,8 +1389,8 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
field_row.setProduct_item_seq_id(product_item_seq_id);
//这儿应该提前生成编号使用
List<Long> itemIds= shopNumberSeqService.batchCreateNextNo("item_id",1);
item_id=itemIds.get(0);
List<Long> itemIds = shopNumberSeqService.batchCreateNextNo("item_id", 1);
item_id = itemIds.get(0);
//item_id = shopNumberSeqService.createNextNo("item_id");
if (null == item_id) {
@ -2074,6 +2074,12 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
Integer product_sale_num = Convert.toInt(product_index_row.get("product_sale_num"));
if (product_base_row != null) {
// 获取店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外 2-停业中3-开业或活动筹备中
Pair<Integer, String> store_biz_state = shopStoreBaseService.getStoreBizState(Convert.toInt(product_base_row.get("store_id")));
product_base_row.put("store_biz_state", store_biz_state.getFirst());
}
//虚拟销量
// todo 是否为商家后台访问
if (null == user || !(user.isAdmin())) {
@ -4989,9 +4995,9 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
shopProductItemSeq.setProduct_item_seq_id(product_item_seq_id);
//这儿应该提前生成编号使用
// item_id = shopNumberSeqService.createNextNo("item_id");
List<Long> itemIds= shopNumberSeqService.batchCreateNextNo("item_id",1);
item_id=itemIds.get(0);
// item_id = shopNumberSeqService.createNextNo("item_id");
List<Long> itemIds = shopNumberSeqService.batchCreateNextNo("item_id", 1);
item_id = itemIds.get(0);
if (null == item_id) {
throw new ApiException(I18nUtil._("生成商品ItemId异常!"));
} else {
@ -5386,7 +5392,7 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
// shopProductIndexList.get(i).setProduct_unit_points(BigDecimal.ZERO);
shopProductIndexList.get(i).setProduct_unit_price_max(base.getProduct_market_price());
shopProductIndexList.get(i).setProduct_unit_sp(Convert.toBigDecimal(base.getProduct_unit_sp()));
// shopProductIndexList.get(i).setProduct_sale_time(base.getProduct_sale_time().getTime());
// shopProductIndexList.get(i).setProduct_sale_time(base.getProduct_sale_time().getTime());
shopProductIndexList.get(i).setProduct_verify_id(base.getProduct_verify_id());
shopProductIndexList.get(i).setProduct_state_id(base.getProduct_state_id());
shopProductIndexList.get(i).setProduct_src_id(existId);
@ -5521,8 +5527,8 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
// 1. 批量新增
if (CollUtil.isNotEmpty(newProducts)) {
// 4. 批量生成新商品的ID
List<Long> newIds=new ArrayList<>();
synchronized (this){
List<Long> newIds = new ArrayList<>();
synchronized (this) {
newIds = shopNumberSeqService.batchCreateNextNo("product_id", newProducts.size());
}
if (newIds == null || newIds.size() != newProducts.size()) {
@ -5945,8 +5951,8 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
List<ShopProductInfo> newShopProductInfoList) {
List<Serializable> itemIds = new ArrayList<>();
if (CollUtil.isEmpty(items)) return itemIds;
List<Long> generatedIds =new ArrayList<>();
synchronized (this){
List<Long> generatedIds = new ArrayList<>();
synchronized (this) {
generatedIds = shopNumberSeqService.batchCreateNextNo("item_id", items.size());
}
// Map<String,String> cacheMap=new HashMap<>();
@ -6469,99 +6475,99 @@ public class ShopProductBaseServiceImpl extends BaseServiceImpl<ShopProductBaseM
@Override
public List<ShopProductBase> findPageMapping(Integer store_id, Integer pageNum, Integer pageSize) {
return shopProductBaseMapper.findPageMapping(store_id,(pageNum-1)*pageSize,pageSize);
return shopProductBaseMapper.findPageMapping(store_id, (pageNum - 1) * pageSize, pageSize);
}
@Override
public Map getProductByProductNumber(String productNumber) {
UserDto userDto= ContextUtil.getCurrentUser();
String store_id=userDto.getStore_id();
UserDto userDto = ContextUtil.getCurrentUser();
String store_id = userDto.getStore_id();
Map data = new HashMap();
QueryWrapper<ShopProductIndex> cond_row = new QueryWrapper<>();
if (StrUtil.isNotBlank(productNumber)) {
cond_row.eq("product_number", productNumber);
}
cond_row.eq("store_id",store_id);
cond_row.eq("store_id", store_id);
List<ShopProductIndex> lists = shopProductIndexService.list(cond_row);
if(lists.isEmpty()){
if (lists.isEmpty()) {
return null;
}
ShopProductIndex shopProductIndexFind=lists.get(0);
ShopProductIndex shopProductIndexFind = lists.get(0);
// todo lc
// data.put("productBase",shopProductIndexFind);
// data.put("productBase",shopProductIndexFind);
//判断是否为商家且开启供应商判断是否已经加入分销中
Long productId= shopProductIndexFind.getProduct_id();
List<Integer> product_ids= Collections.singletonList(Math.toIntExact(productId));
data.put("baseInfo", getProduct(product_ids));
List<Map> baseInfo = (List<Map>) data.get("baseInfo");
Long productId = shopProductIndexFind.getProduct_id();
List<Integer> product_ids = Collections.singletonList(Math.toIntExact(productId));
data.put("baseInfo", getProduct(product_ids));
List<Map> baseInfo = (List<Map>) data.get("baseInfo");
baseInfo.forEach(s -> {
String str_product_spec = (String) s.get("product_spec");
if (StrUtil.isNotBlank(str_product_spec) && StrUtil.equals(str_product_spec, "[]")) {
s.put("product_spec", JSONUtil.parseArray(str_product_spec));
}
String str_product_assist = (String) s.get("product_assist");
if (StrUtil.isNotBlank(str_product_assist) && StrUtil.equals(str_product_assist, "{}")) {
s.put("product_assist", JSONUtil.parseObj(str_product_assist));
}
});
QueryWrapper<ShopProductItem> itemQueryWrapper = new QueryWrapper<>();
itemQueryWrapper.eq("product_id", productId);
List<ShopProductItem> productItems = shopProductItemService.find(itemQueryWrapper);
if (CollectionUtil.isNotEmpty(productItems)) {
for (Map item : baseInfo) {
List<ShopProductItem> shopProductItems = productItems.stream().filter(s -> ObjectUtil.equal(s.getProduct_id(), productId)).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(shopProductItems)) {
BigDecimal product_advice_price = shopProductItems.stream().map(ShopProductItem::getItem_advice_price).min(BigDecimal::compareTo).get();
item.put("product_advice_price", product_advice_price);
}
}
baseInfo.forEach(s -> {
String str_product_spec = (String) s.get("product_spec");
if (StrUtil.isNotBlank(str_product_spec) && StrUtil.equals(str_product_spec, "[]")) {
s.put("product_spec", JSONUtil.parseArray(str_product_spec));
}
List<ShopProductItem> productItemS = productItems.stream().filter(distinctByKey(o -> o.getProduct_id())).collect(Collectors.toList());
data.put("items",productItemS);//商品规格列表
// shop_product_info
ShopProductInfo shopProductInfo = shopProductInfoService.get(productId);
// shop_product_detail
ShopProductDetail shopProductDetail = shopProductDetailService.get(productId);
// shop_product_index
ShopProductIndex shopProductIndex = shopProductIndexService.get(productId);
// shop_product_image
QueryWrapper<ShopProductImage> imageQueryWrapper = new QueryWrapper<>();
imageQueryWrapper.eq("product_id", productId);
List<ShopProductImage> shopProductImages = shopProductImageService.find(imageQueryWrapper);
// shop_product_assist_index
QueryWrapper<ShopProductAssistIndex> indexQueryWrapper = new QueryWrapper<>();
indexQueryWrapper.eq("product_id", productId);
List<ShopProductAssistIndex> assistIndexList = assistIndexService.find(indexQueryWrapper);
HashMap<String, Object> map = new HashMap<>();
if(com.suisung.mall.common.utils.StringUtils.isEmpty(shopProductInfo.getProduct_spec())){
shopProductInfo.setProduct_spec("[]");
String str_product_assist = (String) s.get("product_assist");
if (StrUtil.isNotBlank(str_product_assist) && StrUtil.equals(str_product_assist, "{}")) {
s.put("product_assist", JSONUtil.parseObj(str_product_assist));
}
map.put("shop_product_info", shopProductInfo);
map.put("shop_product_image", shopProductImages);
map.put("shop_product_detail", shopProductDetail);
map.put("shop_product_index", shopProductIndex);
map.put("shop_product_assist_index", assistIndexList);
});
// 虚拟商品信息表
Integer kind_id = shopProductIndex.getKind_id();
if (ObjectUtil.equal(kind_id, StateCode.PRODUCT_KIND_FUWU)) {
ShopProductValidPeriod validPeriod = shopProductValidPeriodService.get(productId);
map.put("shop_product_valid_period", validPeriod);
QueryWrapper<ShopProductItem> itemQueryWrapper = new QueryWrapper<>();
itemQueryWrapper.eq("product_id", productId);
List<ShopProductItem> productItems = shopProductItemService.find(itemQueryWrapper);
if (CollectionUtil.isNotEmpty(productItems)) {
for (Map item : baseInfo) {
List<ShopProductItem> shopProductItems = productItems.stream().filter(s -> ObjectUtil.equal(s.getProduct_id(), productId)).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(shopProductItems)) {
BigDecimal product_advice_price = shopProductItems.stream().map(ShopProductItem::getItem_advice_price).min(BigDecimal::compareTo).get();
item.put("product_advice_price", product_advice_price);
}
}
data.put("productInfo",map);
Integer category_id=productItems.get(0).getCategory_id();
ShopBaseProductCategory shopBaseProductCategory= shopBaseProductCategoryService.get(category_id);
data.put("category",shopBaseProductCategory);
Integer typeId=shopBaseProductCategory.getType_id();
data.put("shopBaseProductType",shopBaseProductTypeService.getType(String.valueOf(typeId), productId)) ;
}
List<ShopProductItem> productItemS = productItems.stream().filter(distinctByKey(o -> o.getProduct_id())).collect(Collectors.toList());
data.put("items", productItemS);//商品规格列表
// shop_product_info
ShopProductInfo shopProductInfo = shopProductInfoService.get(productId);
// shop_product_detail
ShopProductDetail shopProductDetail = shopProductDetailService.get(productId);
// shop_product_index
ShopProductIndex shopProductIndex = shopProductIndexService.get(productId);
// shop_product_image
QueryWrapper<ShopProductImage> imageQueryWrapper = new QueryWrapper<>();
imageQueryWrapper.eq("product_id", productId);
List<ShopProductImage> shopProductImages = shopProductImageService.find(imageQueryWrapper);
// shop_product_assist_index
QueryWrapper<ShopProductAssistIndex> indexQueryWrapper = new QueryWrapper<>();
indexQueryWrapper.eq("product_id", productId);
List<ShopProductAssistIndex> assistIndexList = assistIndexService.find(indexQueryWrapper);
HashMap<String, Object> map = new HashMap<>();
if (com.suisung.mall.common.utils.StringUtils.isEmpty(shopProductInfo.getProduct_spec())) {
shopProductInfo.setProduct_spec("[]");
}
map.put("shop_product_info", shopProductInfo);
map.put("shop_product_image", shopProductImages);
map.put("shop_product_detail", shopProductDetail);
map.put("shop_product_index", shopProductIndex);
map.put("shop_product_assist_index", assistIndexList);
// 虚拟商品信息表
Integer kind_id = shopProductIndex.getKind_id();
if (ObjectUtil.equal(kind_id, StateCode.PRODUCT_KIND_FUWU)) {
ShopProductValidPeriod validPeriod = shopProductValidPeriodService.get(productId);
map.put("shop_product_valid_period", validPeriod);
}
data.put("productInfo", map);
Integer category_id = productItems.get(0).getCategory_id();
ShopBaseProductCategory shopBaseProductCategory = shopBaseProductCategoryService.get(category_id);
data.put("category", shopBaseProductCategory);
Integer typeId = shopBaseProductCategory.getType_id();
data.put("shopBaseProductType", shopBaseProductTypeService.getType(String.valueOf(typeId), productId));
return data;
}

View File

@ -487,7 +487,7 @@ public class ShopProductItemServiceImpl extends BaseServiceImpl<ShopProductItemM
data.put("product_analytics", analytics_row);
// 营业时间段直接影响到 营业状态字段
// store_biz_state 店铺营业状态1-营业中2-已打烊
// store_biz_state 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外2-停业中3-开业或活动筹备中
baseMap.put("store_biz_state", shopStoreBaseService.getStoreBizState(shopStoreBase, shopStoreInfo));
data.put("store_info", baseMap);
@ -2283,7 +2283,7 @@ public class ShopProductItemServiceImpl extends BaseServiceImpl<ShopProductItemM
queryWrapper.or(q -> q.eq("store_id", store_id).eq("item_number", productNumber));
});
queryWrapper.eq("store_id", store_id);
queryWrapper.groupBy("product_id","category_id");
queryWrapper.groupBy("product_id", "category_id");
List<ShopProductItem> shopProductItems = this.list(queryWrapper);
// Map map=shopProductItems.stream().collect(Collectors.toMap(ShopProductItem::getProduct_id,shopProductItem->shopProductItem.getMergedItemId()
// +"_"+shopProductItem.getMergedUnitPrices()));

View File

@ -65,7 +65,7 @@ public class ShopStoreActivityBaseController {
return CommonResult.success(shopStoreActivityBaseService.listBarginItem());
}
@ApiOperation(value = "活动表-编辑", notes = "活动表-编辑")
@ApiOperation(value = "活动表-新增", notes = "活动表-新增")
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public CommonResult edit() {
return shopStoreActivityBaseService.saveActivityBase();

View File

@ -253,8 +253,8 @@ public class StoreController extends BaseControllerImpl {
@ApiOperation(value = "获取附近店铺列表", notes = "获取附近店铺列表")
@RequestMapping(value = "/near/list", method = RequestMethod.GET)
public CommonResult nearStoreList(@RequestParam(value = "provinceId", required = false) String provinceId,
@RequestParam("cityId") String cityId,
@RequestParam("countyId") String countyId,
@RequestParam(value = "cityId", required = false) String cityId,
@RequestParam(value = "countyId", required = false) String countyId,
@RequestParam("userLng") String userLng,
@RequestParam("userLat") String userLat,
@RequestParam(value = "storeCategoryId", required = false) Integer storeCategoryId,

View File

@ -63,11 +63,21 @@ public interface ShopStoreActivityBaseService extends IBaseService<ShopStoreActi
/**
* 验证活动时间是否有效
*
* @param starTime 活动开始时间
* @param endTime 活动结束时间
* @param shopStoreActivityBase
* @param checkTime
* @return
*/
boolean isActivityTimeValid(ShopStoreActivityBase shopStoreActivityBase, Date checkTime);
/**
* 验证活动时间是否有效
*
* @param starTime 活动开始时间
* @param endTime 活动结束时间
* @param checkTime 待验证时间
* @return 时间是否有效
*/
boolean isActivityTimeValid(Date starTime, Date endTime);
boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime);
Map listsMarketing();

View File

@ -17,6 +17,7 @@ import org.springframework.data.util.Pair;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -204,7 +205,7 @@ public interface ShopStoreBaseService extends IBaseService<ShopStoreBase> {
* 修改店铺的营业状态
*
* @param storeId
* @param bizState 营业状态 1-营业2-打烊
* @param bizState 店铺营业状态1-营业中2-已打烊3-开业(活动)筹备中
* @return
*/
Boolean updateStoreBizState(Integer storeId, Integer bizState);
@ -233,7 +234,7 @@ public interface ShopStoreBaseService extends IBaseService<ShopStoreBase> {
*
* @param shopStoreBase 店铺基础信息
* @param shopStoreInfo 店铺详细信息
* @return 店铺营业状态1-营业中2-已打烊
* @return 店铺营业状态1-营业中2-已打烊3-开业(活动)筹备中
*/
Integer getStoreBizState(ShopStoreBase shopStoreBase, ShopStoreInfo shopStoreInfo);
@ -245,6 +246,15 @@ public interface ShopStoreBaseService extends IBaseService<ShopStoreBase> {
*/
Pair<Integer, String> getStoreBizState(Integer storeId);
/**
* 根据一个或多个店铺Id获取开业活动筹备中店铺的营业日期最晚的那个日期
*
* @param storeIds 店铺ID列表多个ID用英文逗号分隔
* @return 最晚的营业开始日期格式为yyyy-MM-dd
*/
Date getLatestBizOpeningDate(String storeIds);
// Page<ShopStoreBase> getMobileStoreList(Integer page, Integer rows);
}

View File

@ -40,7 +40,6 @@ import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.common.utils.UserInfoService;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.activity.service.*;
import com.suisung.mall.shop.base.service.AccountBaseConfigService;
@ -104,8 +103,6 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
@Autowired
private ShopStoreActivityItemService shopStoreActivityItemService;
@Autowired
private ShopBaseActivityTypeService baseActivityTypeService;
@Autowired
private ShopStoreBaseService shopStoreBaseService;
@Autowired
private ShopProductIndexService shopProductIndexService;
@ -116,8 +113,6 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
@Autowired
private ShopProductImageService shopProductImageService;
@Autowired
private RedisService redisService;
@Autowired
private ShopActivityMarketingHistoryService shopActivityMarketingHistoryService;
@Autowired
private UserInfoService userInfoService;
@ -2082,18 +2077,41 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
/**
* 验证活动时间是否有效
*
* @param starTime 活动开始时间
* @param endTime 活动结束时间
* @param shopStoreActivityBase
* @param checkTime
* @return
*/
@Override
public boolean isActivityTimeValid(ShopStoreActivityBase shopStoreActivityBase, Date checkTime) {
if (shopStoreActivityBase == null || checkTime == null) {
return false;
}
Date starTime = shopStoreActivityBase.getActivity_starttime();
Date endTime = shopStoreActivityBase.getActivity_endtime();
return isActivityTimeValid(starTime, endTime, checkTime);
}
/**
* 验证活动时间是否有效
*
* @param starTime 活动开始时间
* @param endTime 活动结束时间
* @param checkTime 待验证时间
* @return 时间是否有效
*/
@Override
public boolean isActivityTimeValid(Date starTime, Date endTime) {
public boolean isActivityTimeValid(Date starTime, Date endTime, Date checkTime) {
if (starTime == null || endTime == null) {
return false;
}
Date now = new Date();
return !now.before(starTime) && !now.after(endTime);
if (checkTime == null) {
checkTime = new Date();
}
return !checkTime.before(starTime) && !checkTime.after(endTime);
}
@Override
@ -3658,7 +3676,7 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
shopStoreActivityBase.setActivity_rule(Convert.toStr(activity_rule));
} else if (base.getActivity_type_id().equals(StateCode.ACTIVITY_TYPE_CUTPRICE)) {
// 砍价活动
activity_rule.put("cut_down_fixed_price", Convert.toBigDecimal(getParameter("cut_down_fixed_price")));// 固定砍价价格
activity_rule.put("cut_down_max_price", Convert.toBigDecimal(getParameter("cut_down_max_price")));// 砍价最高范围
activity_rule.put("cut_down_min_price", Convert.toBigDecimal(getParameter("cut_down_min_price"))); // 砍价最低范围
@ -3668,10 +3686,23 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
activity_rule.put("product_item_name", getParameter("product_item_name"));// 产品名称
activity_rule.put("item_id", getParameter("item_id"));// 产品ID
activity_rule.put("activity_intro", getParameter("activity_intro"));
ShopProductItem item_row = shopProductItemService.get(Convert.toInt(activity_rule.get("item_id")));
ShopProductBase product_row = shopProductBaseService.get(item_row.getProduct_id());
activity_rule.put("product_image", product_row.getProduct_image());
activity_rule.put("item_unit_price", item_row.getItem_unit_price());
// cut_hour - 砍价有效期小时
Integer cut_hour = getParameter("cut_hour", 0);
if (cut_hour > 0) {
activity_rule.put("cut_hour", cut_hour);
}
// product_count - 参与活动商品总数
Integer product_count = getParameter("product_count", 0);
if (product_count > 0) {
activity_rule.put("product_count", product_count);
}
shopStoreActivityBase.setActivity_rule(Convert.toStr(activity_rule));
} else if (base.getActivity_type_id().equals(StateCode.ACTIVITY_TYPE_GIFTBAG)) {
//a+b组合套餐
@ -5320,6 +5351,7 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
data.put("activity_rule", activity_rule);
if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_VOUCHER)) {
//店铺优惠券
String voucher_image = getParameter("voucher_image", "");
BigDecimal needed = getParameter("voucher_points_needed", BigDecimal.ZERO);
BigDecimal subtotal = getParameter("subtotal", BigDecimal.ZERO);
@ -5331,25 +5363,26 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
String voucher_startdate = getParameter("voucher_startdate");
if (!CheckUtil.isDate(voucher_startdate)) {
throw new ApiException(I18nUtil._("请输入有效的生效日期"));
throw new ApiException(I18nUtil._("请输入有效的开始日期"));
}
String voucher_enddate = getParameter("voucher_enddate");
if (!CheckUtil.isDate(voucher_enddate)) {
throw new ApiException(I18nUtil._("请输入有效的失效日期"));
throw new ApiException(I18nUtil._("请输入有效的截止日期"));
}
Map requirement = new HashMap();
Map points = new HashMap();
Map buy = new HashMap();
points.put("needed", needed);
buy.put("subtotal", subtotal);
requirement.put("points", points);
requirement.put("buy", buy);
activity_rule.put("requirement", requirement);
points.put("needed", needed);
buy.put("subtotal", subtotal);
activity_rule.put("requirement", requirement);
activity_rule.put("voucher_image", voucher_image);
activity_rule.put("voucher_price", voucher_price);
activity_rule.put("voucher_start_date", voucher_startdate);
@ -5368,6 +5401,7 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
buy.put("buy", buy);
data.put("requirement", requirement);
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_MARKETING)) {
//市场活动
String start_join_time = getParameter("start_join_time");
String end_join_time = getParameter("end_join_time");
String activity_address = getParameter("activity_address");
@ -5394,6 +5428,7 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
activity_rule.put("activity_detail_intro", activity_detail_intro); // 活动详细规则
activity_rule.put("activity_intro", activity_intro); // 活动介绍
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_LOTTERY)) {
//幸运大抽奖
String lottery_subtitle = getParameter("lottery_subtitle");
Integer lottery_type = getParameter("lottery_type", Integer.class);
String lottery_image = getParameter("lottery_image");
@ -5517,6 +5552,14 @@ public class ShopStoreActivityBaseServiceImpl extends BaseServiceImpl<ShopStoreA
activity_rule.put("product_image", product_row.getProduct_image());
activity_rule.put("item_unit_price", item_row.getItem_unit_price());
// cut_hour - 砍价有效期小时
Integer cut_hour = getParameter("cut_hour", 72);
// product_count - 参与活动商品总数
Integer product_count = getParameter("product_count", 3);
activity_rule.put("product_count", product_count); // 参与活动的商品总数
activity_rule.put("cut_hour", cut_hour); // 砍价的有效期小时
} else if (ObjectUtil.equal(activity_type_id, StateCode.ACTIVITY_TYPE_GIFTBAG)) {
//a+b组合套餐
String activity_bag_category = getParameter("activity_bag_category");//礼包分类

View File

@ -312,8 +312,12 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
row.put("item_id", Convert.toList(Long.class, row.get("item_id")));
row.put("store_latitude", Convert.toDouble(row.get("store_latitude")));
row.put("store_longitude", Convert.toDouble(row.get("store_longitude")));
}
// store_biz_state 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外 2-停业中3-开业或活动筹备中
Pair<Integer, String> retPair = getStoreBizState(Convert.toInt(row.get("store_id")));
row.put("store_biz_state", retPair.getFirst());
}
String default_image = accountBaseConfigService.getConfig("default_image");
if (CollUtil.isNotEmpty(rows)) {
@ -1504,7 +1508,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
store_id = Convert.toInt(user.getStore_id());
}
if (ObjectUtil.isEmpty(store_id)) {
if (CheckUtil.isEmpty(store_id)) {
logger.warn("店铺Id{} 空值,无法获取店铺数据!", store_id);
return new HashMap();
}
@ -2370,9 +2374,16 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
base.setSubsite_id(subsite_id);
}
// 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外 2-停业中3-开业或活动筹备中
Integer storeBizState = Convert.toInt(getParameter("store_biz_state"));
if (storeBizState != null && storeBizState <= 2 && storeBizState >= 1) {
if (storeBizState != null && storeBizState <= 3 && storeBizState >= 1) {
base.setStore_biz_state(storeBizState);
Date storeBizOpeningDate = Convert.toDate(getParameter("store_biz_opening_date"));
if (storeBizState == 3 && storeBizOpeningDate == null) {
return CommonResult.failed("店铺如果开业活动筹备中请输入开业日期yyyy-MM-dd");
}
base.setStore_biz_opening_date(storeBizOpeningDate);
}
// 打包费
@ -2535,9 +2546,16 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
}
// 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外 2-停业中3-开业或活动筹备中
Integer storeBizState = Convert.toInt(getParameter("store_biz_state"));
if (storeBizState != null && storeBizState <= 2 && storeBizState >= 1) {
if (storeBizState != null && storeBizState <= 3 && storeBizState >= 1) {
base.setStore_biz_state(storeBizState);
Date storeBizOpeningDate = Convert.toDate(getParameter("store_biz_opening_date"));
if (storeBizState == 3 && storeBizOpeningDate == null) {
return CommonResult.failed("店铺如果开业活动筹备中请输入开业日期yyyy-MM-dd");
}
base.setStore_biz_opening_date(storeBizOpeningDate);
}
ShopStoreCompany company = null;
@ -4199,14 +4217,14 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
*
* @param shopStoreBase 店铺基础信息
* @param shopStoreInfo 店铺详细信息
* @return 店铺营业状态1-营业中2-已打烊
* @return 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外2-停业中3-开业或活动筹备中
*/
@Override
public Integer getStoreBizState(ShopStoreBase shopStoreBase, ShopStoreInfo shopStoreInfo) {
// 参数校验
if (shopStoreBase == null || shopStoreInfo == null) {
log.warn("店铺基础信息或详细信息为空,无法确定营业状态");
return CommonConstant.Disable2;
log.warn("店铺信息为空,未知营业状态");
return CommonConstant.Store_Biz_State_Closed;
}
try {
@ -4217,21 +4235,25 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 检查店铺是否营业中且营业时间已设置
if (CommonConstant.Enable.equals(storeBizState) && !StrUtil.hasBlank(openingHours, closingHours)) {
// 检查当前时间是否在营业时间内
if (!DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours)) {
// 不在营业时间内返回已打烊状态
return CommonConstant.Disable2;
if (DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours) != 0) {
// 12-开业打烊中但在营业时间外
return CommonConstant.Store_Biz_State_Opening2;
}
return CommonConstant.Enable;
return CommonConstant.Store_Biz_State_Opening;
}
// 返回原始营业状态
Integer resultState = storeBizState != null ? storeBizState : CommonConstant.Store_Biz_State_Closed;
log.debug("返回店铺营业状态storeId: {}, state: {}", shopStoreBase.getStore_id(), resultState);
// 返回原始营业状态
return storeBizState;
} catch (Exception e) {
// 处理异常避免影响主流程
log.error("检查店铺营业状态时发生异常storeId: {}",
shopStoreBase != null ? shopStoreBase.getStore_id() : "unknown", e);
return CommonConstant.Disable2;
return CommonConstant.Store_Biz_State_Closed;
}
}
@ -4239,21 +4261,21 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
* 根据店铺ID获取营业状态
*
* @param storeId 店铺ID
* @return 营业状态1-营业中2-已打烊
* @return 店铺营业状态1-开业营业中且在营业时间内12-开业打烊中但在营业时间外 2-停业中3-开业或活动筹备中
*/
@Override
public Pair<Integer, String> getStoreBizState(Integer storeId) {
// 参数校验
if (CheckUtil.isEmpty(storeId)) {
log.warn("店铺ID为空无法确定营业状态");
return Pair.of(CommonConstant.Disable2, "店铺营业状态有误");
return Pair.of(CommonConstant.Store_Biz_State_Closed, "店铺营业状态有误");
}
try {
StoreBizTimeInfoDTO storeBizTimeInfo = baseMapper.getStoreBizTimeInfo(storeId);
if (storeBizTimeInfo == null) {
log.warn("未找到店铺营业时间信息storeId: {}", storeId);
return Pair.of(CommonConstant.Disable2, "店铺营业状态有误");
return Pair.of(CommonConstant.Store_Biz_State_Closed, "店铺营业状态有误");
}
Integer storeBizState = storeBizTimeInfo.getStore_biz_state();
@ -4261,37 +4283,38 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
String closingHours = storeBizTimeInfo.getStore_close_hours();
// 检查店铺是否营业中且营业时间已设置
if (CommonConstant.Enable.equals(storeBizState)
if (CommonConstant.Store_Biz_State_Opening.equals(storeBizState)
&& StrUtil.isNotBlank(openingHours)
&& StrUtil.isNotBlank(closingHours)) {
// 检查当前时间是否在营业时间内
if (!DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours)) {
if (DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours) != 0) {
// 不在营业时间内返回已打烊状态
log.debug("店铺当前不在营业时间内storeId: {}, openingHours: {}, closingHours: {}",
storeId, openingHours, closingHours);
return Pair.of(CommonConstant.Disable2, String.format("%s营业时间%s-%s", storeBizTimeInfo.getStore_name(),
return Pair.of(CommonConstant.Store_Biz_State_Opening2, String.format("%s营业时间%s-%s", storeBizTimeInfo.getStore_name(),
openingHours, closingHours));
}
return Pair.of(CommonConstant.Enable, "");
return Pair.of(CommonConstant.Store_Biz_State_Opening, "");
}
// 返回原始营业状态处理null情况
Integer resultState = storeBizState != null ? storeBizState : CommonConstant.Disable2;
// 返回原始营业状态
Integer resultState = storeBizState != null ? storeBizState : CommonConstant.Store_Biz_State_Closed;
log.debug("返回店铺营业状态storeId: {}, state: {}", storeId, resultState);
if (resultState == CommonConstant.Disable2) {
return Pair.of(resultState, String.format("%s打烊", storeBizTimeInfo.getStore_name()));
if (CommonConstant.Store_Biz_State_Closed.equals(resultState)) {
return Pair.of(resultState, String.format("%s歇业", storeBizTimeInfo.getStore_name()));
}
// 返回原始营业状态
return Pair.of(resultState, "");
} catch (Exception e) {
// 处理异常避免影响主流程
log.error("检查店铺营业状态发生异常storeId: {}", storeId, e);
return Pair.of(CommonConstant.Disable2, "无法获取店铺营业状态");
return Pair.of(CommonConstant.Store_Biz_State_Closed, "无法获取店铺营业状态");
}
}
/**
* 更新店铺分账比例
*
@ -4334,6 +4357,54 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
/**
* 根据一个或多个店铺Id获取开业活动筹备中店铺的营业日期最晚的那个日期
*
* @param storeIds 店铺ID列表多个ID用英文逗号分隔
* @return 最晚的营业开始日期格式为yyyy-MM-dd
*/
@Override
public Date getLatestBizOpeningDate(String storeIds) {
// 参数校验
if (StrUtil.isBlank(storeIds)) {
return null;
}
try {
// 1. 解析并去重店铺ID
List<Integer> uniqueStoreIds = Arrays.stream(storeIds.split(","))
.map(String::trim)
.filter(StrUtil::isNotBlank)
.map(Integer::valueOf)
.distinct()
.collect(Collectors.toList());
// 2. 如果没有有效的店铺ID返回null
if (uniqueStoreIds.isEmpty()) {
return null;
}
// 3. 查询开业筹备中店铺的最晚营业开始日期
QueryWrapper<ShopStoreBase> queryWrapper = new QueryWrapper<>();
queryWrapper.select("MAX(store_biz_opening_date) AS store_biz_opening_date");
queryWrapper.in("store_id", uniqueStoreIds)
.eq("store_biz_state", CommonConstant.Store_Biz_State_PreActivity)
.eq("store_is_open", CommonConstant.Enable)
.gt("store_biz_opening_date", new Date());
ShopStoreBase shopStoreBase = getOne(queryWrapper);
return shopStoreBase != null ? shopStoreBase.getStore_biz_opening_date() : null;
} catch (NumberFormatException e) {
logger.warn("店铺ID解析失败: storeIds={}", storeIds, e);
return null;
} catch (Exception e) {
logger.error("查询店铺最晚营业开始日期异常: storeIds={}", storeIds, e);
return null;
}
}
// @Override
// public Page<ShopStoreBase> getMobileStoreList(Integer page, Integer rows) {
// QueryWrapper<ShopStoreBase> queryWrapper=new QueryWrapper<>();

View File

@ -1052,9 +1052,10 @@ public class ShopUserCartServiceImpl extends BaseServiceImpl<ShopUserCartMapper,
// 店铺Id
Integer storeId = Convert.toInt(product_row.get("store_id"));
// 判断店铺是否打烊打烊不能放入购物车
// 判断店铺是否歇业停业歇业不能放入购物车
Pair<Integer, String> storeBizState = shopStoreBaseService.getStoreBizState(storeId);
if (storeBizState != null && CommonConstant.Disable2.equals(storeBizState.getFirst())) {
if (storeBizState != null && CommonConstant.Store_Biz_State_Closed.equals(storeBizState.getFirst())) {
throw new ApiException(I18nUtil._(storeBizState.getSecond() + ",无法加购商品。"));
}

View File

@ -145,7 +145,7 @@ logging:
com:
suisung:
mall:
shop: debug
shop: info
sun:
mail: error
baomidou: error

View File

@ -4,7 +4,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
ac_id, activity_id, user_id, ac_sale_price, ac_datetime, order_id, ac_mix_limit_price, ac_num
ac_id, activity_id, user_id, ac_sale_price, ac_datetime, order_id, ac_mix_limit_price, ac_num, expired_at, version
</sql>
</mapper>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.suisung.mall.shop.lakala.mapper.LklReceiveNotifyLogMapper">
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
*
</sql>
</mapper>

View File

@ -3,10 +3,6 @@
<mapper namespace="com.suisung.mall.shop.store.mapper.ShopStoreActivityBaseMapper">
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
activity_id, store_id, user_id, activity_name, activity_title, activity_remark, activity_combo_id,
activity_type_id, activity_starttime, activity_endtime, activity_state, activity_rule, activity_type,
activity_order, activity_is_finish, item_id, subsite_id
</sql>
<sql id="Base_Column_List">*</sql>
</mapper>

View File

@ -5,8 +5,8 @@
<sql id="Base_Column_List">
store_id
, user_id, store_name, store_grade_id, store_logo, store_slogan, store_domain, store_area, store_district_id,
store_address, store_latitude, store_longitude, store_is_selfsupport, store_type, store_is_open, store_biz_state,
ringtone_is_enable, shop_parent_id, store_2nd_category_id,
store_address, store_latitude, store_longitude, store_is_selfsupport, store_type, store_is_open,
store_biz_state, store_biz_opening_date, ringtone_is_enable, shop_parent_id, store_2nd_category_id,
store_category_id, store_state_id, store_time, store_end_time, product_category_ids, store_o2o_tags,
store_o2o_flag, store_o2o_merchant_id, store_circle, subsite_id, lkl_merchant_no, lkl_term_no, wx_qrcode,
split_ratio, packing_fee, created_at, updated_at

View File

@ -20809,7 +20809,7 @@
c();
}, s, o, r));
}) : 17 == i[_x41903[4420]] && $[_x41903[39]](i[_x41903[4617]][_x41903[473]], function(e, t) {
t[_x41903[124]] == a && (s = r = o = 100, publicFun[_x41903[4586]]($(n[_x41903[476]])[_x41903[217]](_x41903[124]), function(e) {
t[_x41903[124]] == a && (s = r = o = 10240, publicFun[_x41903[4586]]($(n[_x41903[476]])[_x41903[217]](_x41903[124]), function(e) {
250 == e[_x41903[686]] && $[_x41903[2030]][_x41903[4089]](e[_x41903[4587]] || __(_x41903[4618])),
t[_x41903[2345]] = e[_x41903[473]][_x41903[688]],
c();

File diff suppressed because one or more lines are too long

40
pom.xml
View File

@ -308,6 +308,7 @@
<docker.host>https://114.132.210.208:2375</docker.host>
<docker.registry>10.1.8.3:5000</docker.registry>
<docker.ca>/Users/panjunjie/code/docker_registry_ca_dev</docker.ca>
<docker.remove_old_image>false</docker.remove_old_image>
<!-- nacos配置 -->
<nacos.server.address>114.132.210.208:8848</nacos.server.address>
<nacos.namespace>public</nacos.namespace>
@ -366,6 +367,7 @@
<docker.host>https://114.132.210.208:2375</docker.host>
<docker.registry>10.1.8.3:5000</docker.registry>
<docker.ca>/Users/panjunjie/code/docker_registry_ca_dev</docker.ca>
<docker.remove_old_image>false</docker.remove_old_image>
<!-- nacos配置 -->
<nacos.server.address>114.132.210.208:8848</nacos.server.address>
<nacos.namespace>public</nacos.namespace>
@ -418,6 +420,7 @@
<docker.host>https://114.132.210.208:2375</docker.host>
<docker.registry>10.1.8.3:5000</docker.registry>
<docker.ca>/Users/panjunjie/code/docker_registry_ca_dev</docker.ca>
<docker.remove_old_image>false</docker.remove_old_image>
<!-- nacos配置 -->
<nacos.server.address>10.1.8.3:8848</nacos.server.address>
<nacos.namespace>public</nacos.namespace>
@ -470,6 +473,7 @@
<docker.host>https://159.75.249.163:2275</docker.host>
<docker.registry>172.16.0.11:5000</docker.registry>
<docker.ca>/Users/panjunjie/code/docker_registry_ca_prod</docker.ca>
<docker.remove_old_image>true</docker.remove_old_image>
<!-- nacos配置 -->
<nacos.server.address>172.16.0.11:8848</nacos.server.address>
<nacos.namespace>public</nacos.namespace>
@ -546,6 +550,20 @@
<version>${docker.maven.plugin.version}</version>
<!--如果想在项目打包时构建镜像添加-->
<executions>
<!-- 先删除已存在的镜像 -->
<execution>
<id>remove-old-image</id>
<phase>clean</phase>
<goals>
<goal>removeImage</goal>
</goals>
<configuration>
<imageName>${docker.registry}/mall/${project.artifactId}:${project.version}</imageName>
</configuration>
<!-- 通过profile属性控制是否执行 -->
<inherited>${docker.remove_old_image}</inherited>
</execution>
<execution>
<id>build-image</id>
<phase>package</phase>
@ -556,18 +574,13 @@
</executions>
<configuration>
<!--定义镜像名称-->
<!-- <imageName>mall/${project.artifactId}:${project.version}</imageName>-->
<!-- &lt;!&ndash; Docker 远程管理地址&ndash;&gt;-->
<!-- <dockerHost>${docker.host}</dockerHost>-->
<imageName>${docker.registry}/mall/${project.artifactId}:${project.version}</imageName>
<!-- Docker 远程管理地址-->
<dockerHost>${docker.host}</dockerHost>
<!--推送镜像仓库校验安全证书,无安全证书无法推送-->
<dockerCertPath>${docker.ca}</dockerCertPath>
<!-- 打包镜像到 docker registry 中心添加认证配置(账号密码在 maven 配置) -->
<pushImage>true</pushImage>
<!--推送镜像仓库校验安全证书,无安全证书无法推送-->
<dockerCertPath>${docker.ca}</dockerCertPath>
<!--定义基础镜像-->
<!-- <baseImage>java:8</baseImage>-->
@ -576,14 +589,21 @@
<!-- 打包镜像到 docker registry 中心添加认证配置(账号密码在 maven 配置) -->
<serverId>docker-registry</serverId>
<!-- 强制覆盖配置 -->
<forceTags>true</forceTags>
<imageTags>
<imageTag>${project.version}</imageTag>
<!-- <imageTag>latest</imageTag>-->
</imageTags>
<!--定义容器启动命令,注意不能换行-->
<entryPoint>["sh", "-c", "mkdir -p /tmp /app/temp /root/nacos/naming/public &amp;&amp; chmod -R 777 /tmp /app/temp /root/nacos &amp;&amp; java -Djava.io.tmpdir=/app/temp -Dnacos.naming.cache.dir=/root/nacos/naming -jar -Xms256m -Xmx512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:+UseContainerSupport -XX:MaxRAMPercentage=60.0 -XX:+UseSerialGC -XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=60 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:./gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M -Dspring.profiles.active=${spring.profile} -Duser.timezone=Asia/Shanghai /${project.build.finalName}.jar"]
</entryPoint>
<!-- 添加额外的Dockerfile指令来清理不必要的文件 -->
<runs>
<run>rm -rf /root/.m2 &amp;&amp; rm -rf /tmp/* &amp;&amp; rm -rf /var/cache/*</run>
</runs>
<!-- <runs>-->
<!-- <run>rm -rf /root/.m2 &amp;&amp; rm -rf /tmp/* &amp;&amp; rm -rf /var/cache/*</run>-->
<!-- </runs>-->
<resources>
<resource>