Merge branch 'main' into dev

This commit is contained in:
liyj 2025-09-24 17:31:41 +08:00
commit 4efd79c180
102 changed files with 53680 additions and 21604 deletions

View File

@ -260,7 +260,7 @@ public class LoginController extends BaseControllerImpl {
return accountUserBaseService.doMerchSmsRegisterAndLogin(userMobile, randKey, verifyCode, cid, osType);
}
@ApiOperation(value = "微信用户一键登录注册")
@ApiOperation(value = "微信用户一键登录注册")
@RequestMapping(value = "/doWxUserRegisterAndLogin", method = RequestMethod.POST)
public CommonResult doWxUserRegisterAndLogin(@RequestBody WxUserInfoReq wxUserInfoReq) {
return accountUserBaseService.doWxUserRegisterAndLogin(wxUserInfoReq);

View File

@ -2429,7 +2429,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
* 根据账号和账号类型获取一条记录
*
* @param userAccount 用户账号
* @param userIsAdmin 用户类型: null-普通用户; 1-管理员; 2-入驻商家;
* @param userIsAdmin 用户类型: null or 0-普通用户; 1-管理员; 2-入驻商家;
* @return AccountUserBase 用户基础信息如果未找到则返回null
*/
public AccountUserBase getByAccountAndType(String userAccount, Integer userIsAdmin) {
@ -3019,7 +3019,6 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(wxUserInfoReq.getPhoneNumber(), wxUserInfoReq.getOpenId())) {
// new ApiException(ResultCode.VALIDATE_INPUTS);
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
@ -3061,7 +3060,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
accountUserBase.setRights_group_id("0");// 店铺管理员,店铺 权限
if (!saveOrUpdate(accountUserBase)) {
return CommonResult.failed(_("用户注册失败!"));
return CommonResult.failed(_("微信注册账号失败!"));
}
Integer userId = accountUserBase.getUser_id();
@ -3153,7 +3152,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
AccountUserBindConnect accountUserBindConnect = accountUserBindConnectService.getBindByBindId(mobile, BindCode.MOBILE, CommonConstant.USER_TYPE_NORMAL);
if (accountUserBindConnect == null || !Objects.equals(accountUserBase.getUser_id(), accountUserBindConnect.getUser_id())) {
// 先绑定手机号
if (accountUserBindConnectService.bindMobileAndOpenId(wxUserInfoReq, accountUserBase.getUser_id(), accountUserBase.getUser_is_admin()) == null) {
if (accountUserBindConnectService.bindMobileAndOpenId(wxUserInfoReq, accountUserBase.getUser_id(), CommonConstant.USER_TYPE_NORMAL) == null) {
return CommonResult.failed(_("账号绑定失败!"));
}
}
@ -3163,7 +3162,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
params.put("client_id", AuthConstant.MOBILE_CLIENT_ID);
params.put("client_secret", AuthConstant.AUTHORITY_MOBILE_SECRET);
params.put("grant_type", "password");
params.put("verify_pwd", "1001"); // 是否验证密码 1001不验证1002验证内部登录没有用户明文密码只能不验证
params.put("verify_pwd", "1001"); // 是否验证密码 1001-不验证1002-验证内部登录没有用户明文密码只能不验证
params.put("username", mobile);
params.put("password", "");
params.put("user_mobile", mobile);

View File

@ -399,7 +399,7 @@ public class AccountUserBindConnectServiceImpl extends BaseServiceImpl<AccountUs
.eq("bind_active", CommonConstant.Enable)
.orderByAsc("bind_time");
AccountUserBindConnect accountUserBindConnect = findOne(queryWrapper);
AccountUserBindConnect accountUserBindConnect = getOne(queryWrapper);
if (accountUserBindConnect != null) {
return accountUserBindConnect;
}
@ -413,7 +413,7 @@ public class AccountUserBindConnectServiceImpl extends BaseServiceImpl<AccountUs
record.setBind_openid(wxUserInfoReq.getOpenId());
record.setBind_unionid(wxUserInfoReq.getUnionId());
record.setBind_gender(wxUserInfoReq.getGender());
record.setBind_nickname(wxUserInfoReq.getNickName());
record.setBind_nickname(wxUserInfoReq.getNickName() + mobile);
record.setBind_icon(wxUserInfoReq.getAvatarUrl());
record.setBind_country(wxUserInfoReq.getCountry());
record.setBind_province(wxUserInfoReq.getProvince());

View File

@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@Service
@ -66,8 +67,8 @@ public class AnalyticsUserServiceImpl implements AnalyticsUserService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (yestodayRegUser.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (todayRegUser.getNum().subtract(yestodayRegUser.getNum())).divide(yestodayRegUser.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (todayRegUser.getNum().subtract(yestodayRegUser.getNum())).divide(yestodayRegUser.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -126,8 +127,8 @@ public class AnalyticsUserServiceImpl implements AnalyticsUserService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -174,78 +174,92 @@ public class OssServiceImpl implements OssService {
String url = null;
Integer uploadType = configService.getConfig("upload", 1);
if (uploadType.equals(1)) {
url = ConfigConstant.URL_BASE + "/admin/oss/upload/" + dir + "/" + uploadName; // 文件本地路径
} else if (uploadType.equals(2)) {
// oss 服务
try {
url = uploadObject2OSS(new File(uploadPath), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(uploadName));
} catch (Exception e) {
e.printStackTrace();
logger.error("文件上传失败!", e);
return null;
}
} else if (uploadType.equals(4)) {
url = uploadObject4OSS(new File(uploadPath), TENGXUN_DEFAULT_DIR.concat("/").concat(dir).concat("/").concat(uploadName));
}
String media_duration = "";
try {
media_duration = VideoUtil.getFormatDuration(uploadPath);
} catch (IOException e) {
throw new ApiException(String.format(I18nUtil._("解析音视频时长异常url【%s】"), uploadPath));
}
//截图
//todo 放入upload中
String thumb_file_url = "";
if (VideoUtil.videoAllowFiles.contains(VideoUtil.getVideoFormat(uploadPath))) {
String cover_path = uploadPath.replace("." + VideoUtil.getVideoFormat(uploadPath), ".jpg");
String cover_upname = uploadName.replace("." + VideoUtil.getVideoFormat(uploadName), ".jpg");
if (VideoUtil.getVideoCover(uploadPath, cover_path, 1, "375*667")) {
if (uploadType.equals(1)) {
url = ConfigConstant.URL_BASE + "/admin/oss/upload/" + dir + "/" + uploadName; // 文件本地路径
} else if (uploadType.equals(2)) {
// oss 服务
try {
thumb_file_url = uploadObject2OSS(new File(cover_path), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat("/").concat(cover_upname));
url = uploadObject2OSS(new File(uploadPath), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(uploadName));
} catch (Exception e) {
e.printStackTrace();
logger.error("文件上传失败!", e);
return null;
}
} else if (uploadType.equals(4)) {
url = uploadObject4OSS(new File(uploadPath), TENGXUN_DEFAULT_DIR.concat("/").concat(dir).concat("/").concat(uploadName));
}
String media_duration = "";
try {
media_duration = VideoUtil.getFormatDuration(uploadPath);
} catch (IOException e) {
throw new ApiException(String.format(I18nUtil._("解析音视频时长异常url【%s】"), uploadPath));
}
//截图
//todo 放入upload中
String thumb_file_url = "";
if (VideoUtil.videoAllowFiles.contains(VideoUtil.getVideoFormat(uploadPath))) {
String cover_path = uploadPath.replace("." + VideoUtil.getVideoFormat(uploadPath), ".jpg");
String cover_upname = uploadName.replace("." + VideoUtil.getVideoFormat(uploadName), ".jpg");
if (VideoUtil.getVideoCover(uploadPath, cover_path, 1, "375*667")) {
try {
thumb_file_url = uploadObject2OSS(new File(cover_path), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat("/").concat(cover_upname));
} catch (Exception e) {
e.printStackTrace();
thumb_file_url = "";
}
} else {
thumb_file_url = "";
}
} else {
thumb_file_url = "";
}
// 保存文件信息
MediaDTO mediaDTO = new MediaDTO();
String name = file.getOriginalFilename();
long size = file.getSize();
mediaDTO.setMedia_name(name);
mediaDTO.setMedia_alt(name);
mediaDTO.setMedia_size(size);
mediaDTO.setMedia_url(url);
mediaDTO.setMedia_order(1);
mediaDTO.setMedia_path("/".concat(dir).concat("/").concat(uploadName));
mediaDTO.setMedia_domain(getOssUrlPrefix().concat(ALIYUN_OSS_DIR_PREFIX));
mediaDTO.setMedia_time(new Date());
mediaDTO.setMedia_desc("");
mediaDTO.setMedia_duration(media_duration);
shopStoreMediaService.saveMedia(mediaDTO, user);
Map result = new HashMap<>();
result.put("media_duration", media_duration);
result.put("media_url", url);
result.put("thumb", thumb_file_url);
return result;
} finally {
// 删除临时文件
try {
File tempFile = new File(uploadPath);
if (tempFile.exists()) {
tempFile.delete();
logger.info("临时文件已删除: {}", uploadPath);
}
// 同时删除可能创建的封面文件
String coverPath = uploadPath.replace("." + VideoUtil.getVideoFormat(uploadPath), ".jpg");
File coverFile = new File(coverPath);
if (coverFile.exists()) {
coverFile.delete();
logger.info("临时封面文件已删除: {}", coverPath);
}
} catch (Exception e) {
logger.warn("删除临时文件失败: {}", uploadPath, e);
}
}
// 保存文件信息
MediaDTO mediaDTO = new MediaDTO();
String name = file.getOriginalFilename();
long size = file.getSize();
mediaDTO.setMedia_name(name);
mediaDTO.setMedia_alt(name);
mediaDTO.setMedia_size(size);
mediaDTO.setMedia_url(url);
mediaDTO.setMedia_order(1);
mediaDTO.setMedia_path("/".concat(dir).concat("/").concat(fileName));
if (uploadType.equals(1)) {
mediaDTO.setMedia_domain(ConfigConstant.URL_BASE);
} else if (uploadType.equals(2)) {
mediaDTO.setMedia_domain(getOssUrlPrefix().concat(ALIYUN_OSS_DIR_PREFIX));
} else if (uploadType.equals(4)) {
mediaDTO.setMedia_domain(TENGXUN_ENDPOINT.concat("/").concat(TENGXUN_DEFAULT_DIR));
}
mediaDTO.setMedia_time(new Date());
mediaDTO.setMedia_desc("");
mediaDTO.setMedia_duration(media_duration);
shopStoreMediaService.saveMedia(mediaDTO, user);
Map result = new HashMap<>();
result.put("media_duration", media_duration);
result.put("media_url", url);
result.put("thumb", thumb_file_url);
return result;
}
public Map upload(MultipartFile file, UserDto user) {

View File

@ -88,12 +88,25 @@ public class CommonConstant {
public static final String PUSH_MSG_CATE_MCH_ORDER_DETAIL = "mchOrderDetail";
public static final String PUSH_MSG_CATE_MCH_ONLINE_ORDER_LIST = "mchOnLineOrderList";
public static final String PUSH_MSG_CATE_MCH_ABNORMAL_ORDER_LIST = "mchAbnormalOrderList";
public static final String PUSH_MSG_CATE_MCH_RETURN_ORDER_LIST = "mchRetrunOrderList";
public static final String PUSH_MSG_CATE_MCH_RETURN_ORDER_LIST = "mchReturnOrderList";
public static final String CONF_KEY_SAME_CITY_ORDER_EXPIRE_SECONDS = "sameCityOrderExpireSeconds";
public static final String SPLIT_ = "diffCityOrderExpireSeconds";
// 订单分拆后 运费和商品子订单前缀
public static final String Sep_DeliveryFee_Prefix = "DF_";
public static final String Sep_GoodsFee_Prefix = "ORD_";
// 分账状态1-已分账2-未分账3-分账已失败
public static final Integer Sta_Separate_Success = 1;
public static final Integer Sta_Separate_Undone = 2;
public static final Integer Sta_Separate_Fail = 3;
// 最低平台内部配送费 配置key
public static final String Inner_Min_DeliveryFee_Key = "inner_min_delivery_fee";
//秒杀活动订阅消息模板id
public static final String BIND_SUB_TMPL_SKILL = "kiDj_hSF_ASwD-Dlgxnypi6IJBQZ12a-hEpd3zZ-Uxc";

View File

@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
@ToString
public class ConfigConstant {
public static final String VERSION = "1.0.1"; //网站版本
public static final String VERSION = "1.0.2"; //网站版本
public static final Integer MAX_LIST_NUM = -1; //最大页码

View File

@ -31,4 +31,11 @@ public class RedisConstant {
public static final String Product_Cate_Key = ConstantRedis.Cache_NameSpace + "product_cate_Key";
public static final String Store_Brand_Key = ConstantRedis.Cache_NameSpace + "store_brand_key";
public static final String SF_Order_Proc_Expire_Key = ConstantRedis.Cache_NameSpace + "sf_order_proc_expire_key__";
public static final String SF_Order_Proc_WillExpire_Key = ConstantRedis.Cache_NameSpace + "sf_order_proc_will_expire_key__";
public static final String Order_Pay_Retry_Count_Key = ConstantRedis.Cache_NameSpace + "order_pay_retry_count:";
}

View File

@ -6,11 +6,18 @@ import com.suisung.mall.common.api.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.stream.Collectors;
/**
* 全局异常处理器
@ -25,22 +32,60 @@ import java.sql.SQLException;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final String UNKNOWN_LOCATION = "未知位置";
private static final String DB_ERROR_MSG = "数据库操作失败,请稍后重试";
private static final String LOG_FORMAT = "业务异常 || URI={} || Method={} || Error={} || Location={}";
/**
* 获取异常发生位置信息
*
* @param stackTrace 异常堆栈
* @return 格式化的位置信息(类名.方法名 : 行号)
* 处理参数校验异常
*/
private String getExceptionLocation(StackTraceElement[] stackTrace) {
if (stackTrace == null || stackTrace.length == 0) {
return UNKNOWN_LOCATION;
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public CommonResult handleValidationException(Exception e, HttpServletRequest req) {
String errorMessage;
if (e instanceof MethodArgumentNotValidException) {
errorMessage = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
} else {
errorMessage = ((BindException) e).getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
}
StackTraceElement first = stackTrace[0];
return String.format("%s.%s:%d", first.getClassName(), first.getMethodName(), first.getLineNumber());
logError(req, "参数校验失败", e);
return CommonResult.validateFailed("数据是否有误,请检查!");
}
/**
* 处理约束违反异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public CommonResult handleConstraintViolation(ConstraintViolationException e, HttpServletRequest req) {
String errorMessage = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
logError(req, "约束违反异常", e);
return CommonResult.validateFailed("系统数据异常,请联系管理员!");
}
/**
* 处理参数类型不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public CommonResult handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException e, HttpServletRequest req) {
String errorMessage = String.format("参数%s类型不匹配需要%s类型但接收到%s",
e.getName(), e.getRequiredType().getSimpleName(), e.getValue());
logError(req, "参数类型不匹配", e);
return CommonResult.validateFailed("参数类型不匹配");
}
/**
* 处理数字格式异常和其他参数异常
*/
@ExceptionHandler({NumberFormatException.class, IllegalArgumentException.class})
public CommonResult handleParameterException(Exception e, HttpServletRequest req) {
logError(req, "参数异常", e);
return CommonResult.validateFailed("数据格式输入有误,请检查!");
}
/**
@ -48,13 +93,12 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler({SQLException.class, DataAccessException.class, Exception.class})
public CommonResult handleSystemException(HttpServletRequest req, Exception e) {
String location = getExceptionLocation(e.getStackTrace());
logger.error(LOG_FORMAT, req.getRequestURI(), req.getMethod(), e.getMessage(), location, e);
logError(req, e.getMessage(), e);
if (e instanceof SQLException || e instanceof DataAccessException) {
return CommonResult.failed(DB_ERROR_MSG);
return CommonResult.failed("系统数据异常,请联系管理员!");
}
return CommonResult.failed(e.getMessage());
return CommonResult.failed("系统内部异常,请联系管理员!");
}
/**
@ -62,12 +106,22 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(ApiException.class)
public CommonResult handleApiException(HttpServletRequest req, ApiException e) {
String location = getExceptionLocation(e.getStackTrace());
logger.error(LOG_FORMAT,
req.getRequestURI(), req.getMethod(), e.getErrorCode(), location, e);
logError(req, e.getErrorCode() != null ? e.getErrorCode().getMessage() : e.getMessage(), e);
return StrUtil.isNotBlank(e.getMessage())
? CommonResult.failed(e.getMessage())
: CommonResult.failed(ResultCode.FAILED);
}
/**
* 记录错误日志
*/
private void logError(HttpServletRequest req, String message, Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
String location = (stackTrace == null || stackTrace.length == 0)
? "未知位置"
: String.format("%s.%s:%d", stackTrace[0].getClassName(), stackTrace[0].getMethodName(), stackTrace[0].getLineNumber());
logger.error("业务异常 || URI={} || Method={} || Error={} || Location={}",
req.getRequestURI(), req.getMethod(), message, location, e);
}
}

View File

@ -185,9 +185,9 @@ public interface PayService {
/**
* 更改交易订单的订单状态和付款状态
*
* @param orderId
* @param orderStateId 空值或0将不更新
* @param tradeIsPaid 空值或0将不更新
* @param orderId 订单Id
* @param orderStateId 订单状态空值或0将不更新
* @param tradeIsPaid 支付付款状态(ENUM):3010-未付款;3011-付款待审核;3012-部分付款;3013-已付款空值或0将不更新
* @return
*/
@PostMapping(value = "/admin/pay/pay-consume-trade/change/state")

View File

@ -311,6 +311,12 @@ public interface ShopService {
boolean lklPayNotifyUpdateShopOrderLkl(@RequestBody JSONObject lklPayNotifyDataJson);
/**
* 终端cos上传文件
*
* @param file
* @return
*/
@PostMapping(value = "/mobile/shop/oss/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
CommonResult uploadFile(@RequestPart(name = "upfile") MultipartFile file);

View File

@ -49,9 +49,6 @@ public class LklOrderSeparate {
@ApiModelProperty(value = "平台订单Id")
private String order_id;
@ApiModelProperty(value = "分账总金额,单位:分")
private String total_amt;
@ApiModelProperty(value = "分账计算类型0- 按照指定金额1- 按照指定比例。默认 0")
private String cal_type;
@ -79,8 +76,14 @@ public class LklOrderSeparate {
@ApiModelProperty(value = "最终分账明细JSON 格式")
private String detail_datas;
@ApiModelProperty(value = "总计分账金额")
private Integer total_separate_value;
@ApiModelProperty(value = "分账发生总金额")
private String total_amt;
@ApiModelProperty(value = "实际分账金额")
private String actual_separate_amt;
@ApiModelProperty(value = "分账费用")
private String total_fee_amt;
@ApiModelProperty(value = "拉卡拉机构编号")
private String lkl_org_no;
@ -91,6 +94,9 @@ public class LklOrderSeparate {
@ApiModelProperty(value = "处理状态ACCEPTED-已受理, PROCESSING-处理中, FAIL-失败, SUCCESS-成功")
private String final_status;
@ApiModelProperty(value = "异步通知数据")
private String notify_resp;
@ApiModelProperty(value = "分账(失败后)的标记")
private String remark;

View File

@ -38,10 +38,18 @@ public class ShopOrderLkl implements Serializable {
private String order_id;
private String out_separate_no;
private Integer total_amt;
private Integer split_amt;
private Integer split_amt_ref;
private Integer shopping_fee;
private Integer shopping_fee_inner;
private BigDecimal split_ratio;
private String payment_time;
@ -52,24 +60,44 @@ public class ShopOrderLkl implements Serializable {
private String trade_status;
private String lkl_trade_no;
private String lkl_log_no;
private String lkl_log_date;
private String lkl_trade_no;
private String lkl_sub_trade_no;
private String lkl_sub_log_no;
private String lkl_receive_trade_no;
private String lkl_receive_log_no;
private String lkl_merchant_no;
private String lkl_term_no;
private String wx_transaction_id; // 微信用户交易单号, 确认收货时使用
private String notify_url;
private String receive_notify_url;
private String lkl_req;
private String lkl_resp;
private String lkl_notify_resp;
private String lkl_receive_notify_resp;
private Integer receive_status;
private Integer separate_status;
private String separate_remark;
private Integer status;
private Date created_at;

View File

@ -78,13 +78,19 @@ public class ShopStoreBase implements Serializable {
@ApiModelProperty(value = "店铺状态(ENUM):0-关闭; 1-运营中, 商品是否可用,需要判断该商品店铺状态")
private Integer store_is_open;
@ApiModelProperty(value = "店铃声开关1-开启2-关闭;")
private Integer ringtone_is_enable;
@ApiModelProperty(value = "店铺营业状态1-营业中2-已打烊;")
private Integer store_biz_state;
@ApiModelProperty(value = "上级店铺编号:创建店铺决定,所属分销商-不可更改! 佣金公平性考虑")
private Integer shop_parent_id;
@ApiModelProperty(value = "店铺分类编号")
@ApiModelProperty(value = "店铺二级分类编号")
private Integer store_2nd_category_id;
@ApiModelProperty(value = "店铺一级分类编号")
private Integer store_category_id;
@ApiModelProperty(value = "店铺资料信息状态(ENUM):3210-待完善资料; 3220-等待审核; 3230-资料审核没有通过; 3240-资料审核通过,待付款")

View File

@ -0,0 +1,756 @@
package com.suisung.mall.common.pojo.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 拉卡拉订单分账信息处理类
* 实现基于总金额和基于可分账金额的分账算法支持平台代理商和商家的多级分账
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class LklSeparateDTO {
// 基础金额属性
private Integer totalSeparateAmount; // 分账总金额()
private Integer canSeparateAmount; // 可分账金额()
private Integer refCanSeparateAmount; // 拉卡拉参考可分账金额()
private Integer shippingFee; // 配送费()
// 分账比例属性
private BigDecimal lklRatio; // 拉卡拉分账比例( 0.0025=0.25%)
private BigDecimal mchRatio; // 商户分账比例( 0.96=96%)
private BigDecimal platRatio; // 平台分账比例( 0.01=1%)
private BigDecimal agent1stRatio; // 一级代理商分账比例( 0.01=1%)
private BigDecimal agent2ndRatio; // 二级代理商分账比例( 0.03=3%)
// 分账金额结果属性
private Integer lklAmount; // 拉卡拉分账金额()
private Integer mchAmount; // 商户分账金额()
private Integer platAmount; // 平台分账金额()
private Integer agent1stAmount; // 一级代理商分账金额()
private Integer agent2ndAmount; // 二级代理商分账金额()
/**
* 测试方法
*/
public static void main(String[] args) {
// 创建分账对象
LklSeparateDTO dto = new LklSeparateDTO();
// 设置测试参数 - 正常情况
dto.setTotalSeparateAmount(1000); // 分账总额 1000分
dto.setShippingFee(100); // 配送费 100分
dto.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉分账比例 0.0025
dto.setMchRatio(new BigDecimal("0.94")); // 商家分账比例 0.94
dto.setPlatRatio(new BigDecimal("0.06")); // 平台分账比例 0.01
// dto.setAgent1stRatio(new BigDecimal("0.01")); // 一级代理商分账比例 0.01
// dto.setAgent2ndRatio(new BigDecimal("0.01")); // 二级代理商分账比例 0.01
// 执行基于总金额的分账算法
System.out.println("=== 基于总金额的分账算法测试(正常情况) ===");
SharingResult result1 = dto.sharingOnTotalAmount();
System.out.println(result1);
if (result1.isSuccess()) {
printSharingDetails(dto, "基于总金额");
}
// 测试基于可分账金额的分账算法正常情况
System.out.println("\n=== 基于可分账金额的分账算法测试(正常情况) ===");
LklSeparateDTO dto2 = new LklSeparateDTO();
dto2.setTotalSeparateAmount(1); // 分账总额 1000分
dto2.setShippingFee(0); // 配送费 100分
// dto2.setRefCanSeparateAmount(null);
dto2.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉分账比例 0.0025
dto2.setMchRatio(new BigDecimal("0.94")); // 商家分账比例 0.857 (会产生小数)
dto2.setPlatRatio(new BigDecimal("0.01")); // 平台分账比例 0.01
// 不设置一级和二级代理商分账比例测试不参与分账的情况
// dto2.setAgent1stRatio(new BigDecimal("0.01")); // 一级代理商分账比例 0.023 (会产生小数)
// dto2.setAgent2ndRatio(new BigDecimal("0.04")); // 二级代理商分账比例 0.031 (会产生小数)
SharingResult result2 = dto2.sharingOnCanSeparateAmount();
System.out.println(result2);
if (result2.isSuccess()) {
printSharingDetails(dto2, "基于可分账金额");
}
// 测试toJSON和toString方法
System.out.println("\n=== toJSON和toString方法测试 ===");
System.out.println("基于可分账金额算法的JSON输出:");
System.out.println(dto2.toJSON());
System.out.println("\n基于可分账金额算法的toString输出:");
System.out.println(dto2);
}
/**
* 计算百分比
*
* @param amount 金额
* @param total 总金额
* @return 百分比字符串保留两位小数
*/
public static String calculatePercentage(Integer amount, Integer total) {
if (amount == null || total == null || total == 0) {
return "0.00";
}
BigDecimal percentage = new BigDecimal(amount).multiply(new BigDecimal("100"))
.divide(new BigDecimal(total), 2, RoundingMode.HALF_UP);
return percentage.toString();
}
/**
* 打印分账详情
*
* @param dto 分账对象
* @param type 分账类型
*/
private static void printSharingDetails(LklSeparateDTO dto, String type) {
System.out.println(type + "分账详情:");
if (dto.getTotalSeparateAmount() != null) {
System.out.println(" 分账总金额: " + dto.getTotalSeparateAmount() + "");
}
// 打印拉卡拉分账金额即使为0也要显示
if (dto.getLklAmount() != null) {
System.out.println(" 拉卡拉分账金额: " + dto.getLklAmount() + "" +
" (占比: " + calculatePercentage(dto.getLklAmount(), dto.getTotalSeparateAmount()) + "%)");
}
// 打印配送费即使为0也要显示
if (dto.getShippingFee() != null) {
System.out.println(" 配送费: " + dto.getShippingFee() + "" +
" (占比: " + calculatePercentage(dto.getShippingFee(), dto.getTotalSeparateAmount()) + "%)");
}
if (dto.getCanSeparateAmount() != null) {
System.out.println(" 可分账金额: " + dto.getCanSeparateAmount() + "");
}
if (dto.getRefCanSeparateAmount() != null) {
System.out.println(" 参考可分账金额: " + dto.getRefCanSeparateAmount() + "");
}
if (dto.getCanSeparateAmount() != null && dto.getRefCanSeparateAmount() != null) {
System.out.println(" 参考可分账差值: " + (dto.getRefCanSeparateAmount() - dto.getCanSeparateAmount()) + "");
}
if (dto.getPlatAmount() != null) {
System.out.println(" 平台分账金额: " + dto.getPlatAmount() + "" +
(dto.getTotalSeparateAmount() != null ?
" (总金额占比: " + calculatePercentage(dto.getPlatAmount(), dto.getTotalSeparateAmount()) + "%)" : "") +
(dto.getCanSeparateAmount() != null && dto.getCanSeparateAmount() > 0 ?
" (可分账占比: " + calculatePercentage(dto.getPlatAmount(), dto.getCanSeparateAmount()) + "%)" : ""));
}
if (dto.getAgent1stAmount() != null) {
System.out.println(" 一级代理商分账金额: " + dto.getAgent1stAmount() + "" +
(dto.getTotalSeparateAmount() != null ?
" (总金额占比: " + calculatePercentage(dto.getAgent1stAmount(), dto.getTotalSeparateAmount()) + "%)" : "") +
(dto.getCanSeparateAmount() != null && dto.getCanSeparateAmount() > 0 ?
" (可分账占比: " + calculatePercentage(dto.getAgent1stAmount(), dto.getCanSeparateAmount()) + "%)" : ""));
}
if (dto.getAgent2ndAmount() != null) {
System.out.println(" 二级代理商分账金额: " + dto.getAgent2ndAmount() + "" +
(dto.getTotalSeparateAmount() != null ?
" (总金额占比: " + calculatePercentage(dto.getAgent2ndAmount(), dto.getTotalSeparateAmount()) + "%)" : "") +
(dto.getCanSeparateAmount() != null && dto.getCanSeparateAmount() > 0 ?
" (可分账占比: " + calculatePercentage(dto.getAgent2ndAmount(), dto.getCanSeparateAmount()) + "%)" : ""));
}
if (dto.getMchAmount() != null) {
System.out.println(" 商家分账金额: " + dto.getMchAmount() + "" +
(dto.getTotalSeparateAmount() != null ?
" (总金额占比: " + calculatePercentage(dto.getMchAmount(), dto.getTotalSeparateAmount()) + "%)" : "") +
(dto.getCanSeparateAmount() != null && dto.getCanSeparateAmount() > 0 ?
" (可分账占比: " + calculatePercentage(dto.getMchAmount(), dto.getCanSeparateAmount()) + "%)" : ""));
}
}
/**
* 分账算法 - 基于总金额进行分账
*
* @return SharingResult 分账结果包含是否成功和失败原因
*/
public SharingResult sharingOnTotalAmount() {
// 执行基于总金额的分账计算
SharingCalculationResult result = calculateBasedOnTotalAmount();
if (result.isSuccess()) {
// 更新实例变量
this.lklAmount = result.getLklAmount();
this.canSeparateAmount = result.getCanSeparateAmount();
this.refCanSeparateAmount = result.getRefCanSeparateAmount();
this.shippingFee = result.getShippingFee();
this.platAmount = result.getPlatAmount();
this.agent1stAmount = result.getAgent1stAmount();
this.agent2ndAmount = result.getAgent2ndAmount();
this.mchAmount = result.getMchAmount();
return new SharingResult(true, null);
} else {
return new SharingResult(false, result.getErrorMessage());
}
}
/**
* 基于总金额的分账计算独立算法
*
* @return SharingCalculationResult 计算结果
*/
private SharingCalculationResult calculateBasedOnTotalAmount() {
// 1. 参数校验
if (totalSeparateAmount == null || totalSeparateAmount <= 0) {
return new SharingCalculationResult(false, "分账总金额不能为空或小于等于0");
}
// 2. 计算拉卡拉分账金额
int lklAmount = calculateAmountWithRatio(lklRatio, totalSeparateAmount, RoundingMode.HALF_UP);
// 3. 处理配送费可以为空或0表示免配送费
int effectiveShippingFee = (shippingFee != null && shippingFee > 0) ? shippingFee : 0;
// 4. 检查配送费是否合法不能大于等于可分账金额
if (effectiveShippingFee >= totalSeparateAmount - lklAmount) {
return new SharingCalculationResult(false, "配送费不能大于等于可分账金额");
}
// 5. 计算可分账金额总金额 - 拉卡拉分账金额 - 配送费
int availableAmount = totalSeparateAmount - lklAmount - effectiveShippingFee;
if (availableAmount < 0) {
return new SharingCalculationResult(false, "可分账金额不能为负数");
}
int canSeparateAmount = availableAmount;
// 保持canSeparateAmount和refCanSeparateAmount的独立性不进行相互赋值
// 记录refCanSeparateAmount相关信息
System.out.println("基于总金额算法 - 可分账金额计算完成: canSeparateAmount=" + canSeparateAmount +
", refCanSeparateAmount=" + this.refCanSeparateAmount);
// 5.1 检查refCanSeparateAmount是否为有效值如果是有效值且与计算出的canSeparateAmount不一致则使用refCanSeparateAmount作为分账金额
int separateAmountForCalculation = canSeparateAmount; // 默认使用计算出的可分账金额
if (this.refCanSeparateAmount != null && this.refCanSeparateAmount > 0 && this.refCanSeparateAmount < totalSeparateAmount) {
// refCanSeparateAmount是非空非零且比totalSeparateAmount小的有效值
if (!this.refCanSeparateAmount.equals(canSeparateAmount)) {
// 有效值如果和计算出来的canSeparateAmount的值不一致
separateAmountForCalculation = this.refCanSeparateAmount; // 使用refCanSeparateAmount作为分账算法中的可分金额
System.out.println("基于总金额算法 - 使用refCanSeparateAmount作为分账金额: refCanSeparateAmount=" +
this.refCanSeparateAmount + ", canSeparateAmount=" + canSeparateAmount +
", 差值=" + (this.refCanSeparateAmount - canSeparateAmount));
} else {
System.out.println("基于总金额算法 - refCanSeparateAmount与canSeparateAmount相等无需调整");
}
} else {
System.out.println("基于总金额算法 - refCanSeparateAmount无效使用计算出的canSeparateAmount");
}
// 6. 初始化各分账金额
int platAmount = 0;
int agent1stAmount = 0;
int agent2ndAmount = 0;
int mchAmount = 0;
// 7. 根据参与方动态调整分账比例基于总金额的独立逻辑
BigDecimal effectivePlatRatio = platRatio != null ? platRatio : BigDecimal.ZERO;
BigDecimal effectiveAgent1stRatio = agent1stRatio != null ? agent1stRatio : BigDecimal.ZERO;
BigDecimal effectiveAgent2ndRatio = agent2ndRatio != null ? agent2ndRatio : BigDecimal.ZERO;
BigDecimal effectiveMchRatio = mchRatio != null ? mchRatio : BigDecimal.ZERO;
// 判断代理商是否参与分账在调整比例前进行判断
boolean hasAgent1st = effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0;
boolean hasAgent2nd = effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0;
// 根据商家分账比例动态调整其他方比例独立于基于可分账金额的算法
if (effectiveMchRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal remainingRatio = BigDecimal.ONE.subtract(effectiveMchRatio);
BigDecimal onePercent = new BigDecimal("0.01");
if (hasAgent1st && hasAgent2nd) {
// 平台一级代理商二级代理商都参与
// 平台固定1%一级代理商固定1%二级代理商为剩余比例
effectivePlatRatio = onePercent;
effectiveAgent1stRatio = onePercent;
effectiveAgent2ndRatio = remainingRatio.subtract(onePercent).subtract(onePercent);
} else if (hasAgent1st || hasAgent2nd) {
// 只有平台和一个代理商参与
effectivePlatRatio = onePercent;
// 代理商为剩余比例
if (hasAgent1st) {
effectiveAgent1stRatio = remainingRatio.subtract(onePercent);
} else {
effectiveAgent2ndRatio = remainingRatio.subtract(onePercent);
}
} else {
// 只有平台参与
effectivePlatRatio = remainingRatio;
}
// 重新判断代理商是否参与分账基于调整后的比例
hasAgent1st = effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0;
hasAgent2nd = effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0;
}
// 如果商家比例不存在或为0使用各自设置的固定比例代理商参与状态已在前面确定
// 8. 按优先级顺序计算各分账方金额
// 平台分账优先级1- 基于总金额计算
if (effectivePlatRatio.compareTo(BigDecimal.ZERO) > 0) {
platAmount = calculateAmountWithRatio(effectivePlatRatio, totalSeparateAmount, RoundingMode.HALF_UP);
// 确保平台分账金额不超过可分账金额
if (platAmount > separateAmountForCalculation) {
platAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= platAmount;
}
// 一级代理商分账优先级2- 基于总金额计算
if (hasAgent1st && effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
agent1stAmount = calculateAmountWithRatio(effectiveAgent1stRatio, totalSeparateAmount, RoundingMode.HALF_UP);
// 确保不超过剩余金额
if (agent1stAmount > separateAmountForCalculation) {
agent1stAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= agent1stAmount;
}
// 二级代理商分账优先级3- 基于总金额计算
if (hasAgent2nd && effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
agent2ndAmount = calculateAmountWithRatio(effectiveAgent2ndRatio, totalSeparateAmount, RoundingMode.HALF_UP);
// 确保不超过剩余金额
if (agent2ndAmount > separateAmountForCalculation) {
agent2ndAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= agent2ndAmount;
}
// 9. 商家分账优先级4获得剩余所有金额
mchAmount = separateAmountForCalculation;
// 10. 检查商家分账金额是否合法
if (mchAmount < 0) {
return new SharingCalculationResult(false, "商家分账金额不能为负数");
}
// 创建并返回计算结果
SharingCalculationResult calculationResult = new SharingCalculationResult(true, null);
calculationResult.setLklAmount(lklAmount);
calculationResult.setCanSeparateAmount(canSeparateAmount);
calculationResult.setRefCanSeparateAmount(this.refCanSeparateAmount);
calculationResult.setShippingFee(effectiveShippingFee);
calculationResult.setPlatAmount(platAmount);
calculationResult.setAgent1stAmount(agent1stAmount);
calculationResult.setAgent2ndAmount(agent2ndAmount);
calculationResult.setMchAmount(mchAmount);
return calculationResult;
}
/**
* 分账算法 - 基于可分账金额进行分账
*
* @return SharingResult 分账结果包含是否成功和失败原因
*/
public SharingResult sharingOnCanSeparateAmount() {
// 执行基于可分账金额的分账计算
SharingCalculationResult result = calculateBasedOnCanSeparateAmount();
if (result.isSuccess()) {
// 更新实例变量
this.lklAmount = result.getLklAmount();
this.canSeparateAmount = result.getCanSeparateAmount();
this.refCanSeparateAmount = result.getRefCanSeparateAmount();
this.shippingFee = result.getShippingFee();
this.platAmount = result.getPlatAmount();
this.agent1stAmount = result.getAgent1stAmount();
this.agent2ndAmount = result.getAgent2ndAmount();
this.mchAmount = result.getMchAmount();
return new SharingResult(true, null);
} else {
return new SharingResult(false, result.getErrorMessage());
}
}
/**
* 基于可分账金额的分账计算独立算法
*
* @return SharingCalculationResult 计算结果
*/
private SharingCalculationResult calculateBasedOnCanSeparateAmount() {
// 1. 参数校验
if (totalSeparateAmount == null || totalSeparateAmount <= 0) {
return new SharingCalculationResult(false, "分账总金额不能为空或小于等于0");
}
// 2. 先扣除配送费可以为空或0表示免配送费
int effectiveShippingFee = (shippingFee != null && shippingFee > 0) ? shippingFee : 0;
if (effectiveShippingFee >= totalSeparateAmount) {
return new SharingCalculationResult(false, "配送费不能大于等于分账总金额");
}
// 3. 计算扣除配送费后的金额
int amountAfterShipping = totalSeparateAmount - effectiveShippingFee;
// 4. 计算拉卡拉分账金额手续费= 分账总金额 * 拉卡拉分账比例
int lklAmount = calculateAmountWithRatio(lklRatio, totalSeparateAmount, RoundingMode.HALF_UP);
// 5. 检查拉卡拉分账金额是否合法
if (lklAmount >= amountAfterShipping) {
return new SharingCalculationResult(false, "拉卡拉分账金额不能大于等于扣除配送费后的金额");
}
// 6. 计算可分账金额 = 分账总金额 - 配送费 - 拉卡拉分账金额
int availableAmount = amountAfterShipping - lklAmount;
if (availableAmount < 0) {
return new SharingCalculationResult(false, "可分账金额不能为负数");
}
int canSeparateAmount = availableAmount;
// 记录refCanSeparateAmount相关信息
System.out.println("基于可分账金额算法 - 可分账金额计算完成: canSeparateAmount=" + canSeparateAmount +
", refCanSeparateAmount=" + this.refCanSeparateAmount);
// 6.1 检查refCanSeparateAmount是否为有效值如果是有效值且与计算出的canSeparateAmount不一致则使用refCanSeparateAmount作为分账金额
int separateAmountForCalculation = canSeparateAmount; // 默认使用计算出的可分账金额
if (this.refCanSeparateAmount != null && this.refCanSeparateAmount > 0 && this.refCanSeparateAmount < totalSeparateAmount) {
// refCanSeparateAmount是非空非零且比totalSeparateAmount小的有效值
if (!this.refCanSeparateAmount.equals(canSeparateAmount)) {
// 有效值如果和计算出来的canSeparateAmount的值不一致
separateAmountForCalculation = this.refCanSeparateAmount; // 使用refCanSeparateAmount作为分账算法中的可分金额
System.out.println("基于可分账金额算法 - 使用refCanSeparateAmount作为分账金额: refCanSeparateAmount=" +
this.refCanSeparateAmount + ", canSeparateAmount=" + canSeparateAmount +
", 差值=" + (this.refCanSeparateAmount - canSeparateAmount));
} else {
System.out.println("基于可分账金额算法 - refCanSeparateAmount与canSeparateAmount相等无需调整");
}
} else {
System.out.println("基于可分账金额算法 - refCanSeparateAmount无效使用计算出的canSeparateAmount");
}
// 7. 初始化各分账金额
int platAmount = 0;
int agent1stAmount = 0;
int agent2ndAmount = 0;
int mchAmount = 0;
// 8. 根据参与方动态调整分账比例基于可分账金额的独立逻辑
BigDecimal effectivePlatRatio = platRatio != null ? platRatio : BigDecimal.ZERO;
BigDecimal effectiveAgent1stRatio = agent1stRatio != null ? agent1stRatio : BigDecimal.ZERO;
BigDecimal effectiveAgent2ndRatio = agent2ndRatio != null ? agent2ndRatio : BigDecimal.ZERO;
BigDecimal effectiveMchRatio = mchRatio != null ? mchRatio : BigDecimal.ZERO;
// 判断代理商是否参与分账在调整比例前进行判断
boolean hasAgent1st = effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0;
boolean hasAgent2nd = effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0;
// 根据商家分账比例动态调整其他方比例独立于基于总金额的算法
if (effectiveMchRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal remainingRatio = BigDecimal.ONE.subtract(effectiveMchRatio);
BigDecimal onePercent = new BigDecimal("0.01");
if (hasAgent1st && hasAgent2nd) {
// 平台一级代理商二级代理商都参与
// 平台固定1%一级代理商固定1%二级代理商为剩余比例
effectivePlatRatio = onePercent;
effectiveAgent1stRatio = onePercent;
effectiveAgent2ndRatio = remainingRatio.subtract(onePercent).subtract(onePercent);
} else if (hasAgent1st || hasAgent2nd) {
// 只有平台和一个代理商参与
effectivePlatRatio = onePercent;
// 代理商为剩余比例
if (hasAgent1st) {
effectiveAgent1stRatio = remainingRatio.subtract(onePercent);
} else {
effectiveAgent2ndRatio = remainingRatio.subtract(onePercent);
}
} else {
// 只有平台参与
effectivePlatRatio = remainingRatio;
}
// 重新判断代理商是否参与分账基于调整后的比例
hasAgent1st = effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0;
hasAgent2nd = effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0;
}
// 如果商家比例不存在或为0使用各自设置的固定比例代理商参与状态已在前面确定
// 9. 按新的优先级顺序计算各分账方金额基于可分账金额计算
// 商家分账优先级1- 基于可分账金额计算采用向上进位方式
if (effectiveMchRatio.compareTo(BigDecimal.ZERO) > 0) {
mchAmount = calculateAmountWithRatio(effectiveMchRatio, canSeparateAmount, RoundingMode.UP);
// 确保商家分账金额不超过剩余金额
if (mchAmount > separateAmountForCalculation) {
mchAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= mchAmount;
}
// 平台分账优先级2- 基于可分账金额计算采用四舍五入方式
if (effectivePlatRatio.compareTo(BigDecimal.ZERO) > 0) {
platAmount = calculateAmountWithRatio(effectivePlatRatio, canSeparateAmount, RoundingMode.HALF_UP);
// 确保平台分账金额不超过剩余金额
if (platAmount > separateAmountForCalculation) {
platAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= platAmount;
}
// 一级代理商分账优先级3- 基于可分账金额计算采用四舍五入方式
if (hasAgent1st && effectiveAgent1stRatio.compareTo(BigDecimal.ZERO) > 0) {
agent1stAmount = calculateAmountWithRatio(effectiveAgent1stRatio, canSeparateAmount, RoundingMode.HALF_UP);
// 确保不超过剩余金额
if (agent1stAmount > separateAmountForCalculation) {
agent1stAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= agent1stAmount;
}
// 二级代理商分账优先级4- 基于可分账金额计算采用四舍五入方式
if (hasAgent2nd && effectiveAgent2ndRatio.compareTo(BigDecimal.ZERO) > 0) {
agent2ndAmount = calculateAmountWithRatio(effectiveAgent2ndRatio, canSeparateAmount, RoundingMode.HALF_UP);
// 确保不超过剩余金额
if (agent2ndAmount > separateAmountForCalculation) {
agent2ndAmount = separateAmountForCalculation;
}
separateAmountForCalculation -= agent2ndAmount;
}
// 10. 如果还有剩余金额将其分配给商家
if (separateAmountForCalculation > 0) {
mchAmount += separateAmountForCalculation;
}
// 11. 检查商家分账金额是否合法
if (mchAmount < 0) {
return new SharingCalculationResult(false, "商家分账金额不能为负数");
}
// 创建并返回计算结果
SharingCalculationResult calculationResult = new SharingCalculationResult(true, null);
calculationResult.setLklAmount(lklAmount);
calculationResult.setCanSeparateAmount(canSeparateAmount);
calculationResult.setRefCanSeparateAmount(this.refCanSeparateAmount);
calculationResult.setShippingFee(effectiveShippingFee);
calculationResult.setPlatAmount(platAmount);
calculationResult.setAgent1stAmount(agent1stAmount);
calculationResult.setAgent2ndAmount(agent2ndAmount);
calculationResult.setMchAmount(mchAmount);
return calculationResult;
}
/**
* 将分账信息转换为格式化的JSON字符串
*
* @return JSON格式的字符串
*/
public String toJSON() {
StringBuilder sb = new StringBuilder();
sb.append("{");
// 基础金额信息
sb.append("基础金额信息:{");
sb.append("分账总金额:").append(totalSeparateAmount != null ? totalSeparateAmount : 0).append("分,");
sb.append("配送费:").append(shippingFee != null ? shippingFee : 0);
sb.append("},");
// 分账比例信息
sb.append("分账比例信息:{");
sb.append("拉卡拉分账比例:").append(lklRatio != null ? lklRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("商家分账比例:").append(mchRatio != null ? mchRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("平台分账比例:").append(platRatio != null ? platRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("一级代理商分账比例:").append(agent1stRatio != null ? agent1stRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("二级代理商分账比例:").append(agent2ndRatio != null ? agent2ndRatio.stripTrailingZeros().toPlainString() : "0");
sb.append("},");
// 分账金额结果
sb.append("分账金额结果:{");
sb.append("拉卡拉分账金额:").append(lklAmount != null ? lklAmount : 0).append("分,");
sb.append("可分账金额:").append(canSeparateAmount != null ? canSeparateAmount : 0).append("分,");
sb.append("参考可分账金额:").append(refCanSeparateAmount != null ? refCanSeparateAmount : 0).append("分,");
if (canSeparateAmount != null && refCanSeparateAmount != null) {
sb.append("参考可分账差值:").append(refCanSeparateAmount - canSeparateAmount).append("分,");
}
sb.append("配送费:").append(shippingFee != null ? shippingFee : 0).append("分,");
sb.append("平台分账金额:").append(platAmount != null ? platAmount : 0).append("分,");
sb.append("一级代理商分账金额:").append(agent1stAmount != null ? agent1stAmount : 0).append("分,");
sb.append("二级代理商分账金额:").append(agent2ndAmount != null ? agent2ndAmount : 0).append("分,");
sb.append("商家分账金额:").append(mchAmount != null ? mchAmount : 0).append("");
sb.append("}");
sb.append("}");
return sb.toString();
}
/**
* 根据比例计算金额
*
* @param ratio 比例
* @param baseAmount 基础金额
* @param roundingMode 舍入模式
* @return 计算后的金额
*/
private int calculateAmountWithRatio(BigDecimal ratio, int baseAmount, RoundingMode roundingMode) {
if (ratio == null || ratio.compareTo(BigDecimal.ZERO) <= 0) {
return 0;
}
return ratio.multiply(new BigDecimal(baseAmount))
.setScale(0, roundingMode).intValue();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LklSeparateDTO{");
// 基础金额信息
sb.append("基础金额信息:{");
sb.append("分账总金额:").append(totalSeparateAmount != null ? totalSeparateAmount : 0).append("分,");
sb.append("配送费:").append(shippingFee != null ? shippingFee : 0);
sb.append("},");
// 分账比例信息
sb.append("分账比例信息:{");
sb.append("拉卡拉分账比例:").append(lklRatio != null ? lklRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("商家分账比例:").append(mchRatio != null ? mchRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("平台分账比例:").append(platRatio != null ? platRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("一级代理商分账比例:").append(agent1stRatio != null ? agent1stRatio.stripTrailingZeros().toPlainString() : "0").append(",");
sb.append("二级代理商分账比例:").append(agent2ndRatio != null ? agent2ndRatio.stripTrailingZeros().toPlainString() : "0");
sb.append("},");
// 分账金额结果
sb.append("分账金额结果:{");
sb.append("拉卡拉分账金额:").append(lklAmount != null ? lklAmount : 0).append("分,");
sb.append("可分账金额:").append(canSeparateAmount != null ? canSeparateAmount : 0).append("分,");
sb.append("参考可分账金额:").append(refCanSeparateAmount != null ? refCanSeparateAmount : 0).append("分,");
if (canSeparateAmount != null && refCanSeparateAmount != null) {
sb.append("参考可分账差值:").append(refCanSeparateAmount - canSeparateAmount).append("分,");
}
sb.append("配送费:").append(shippingFee != null ? shippingFee : 0).append("分,");
sb.append("平台分账金额:").append(platAmount != null ? platAmount : 0).append("分,");
sb.append("一级代理商分账金额:").append(agent1stAmount != null ? agent1stAmount : 0).append("分,");
sb.append("二级代理商分账金额:").append(agent2ndAmount != null ? agent2ndAmount : 0).append("分,");
sb.append("商家分账金额:").append(mchAmount != null ? mchAmount : 0).append("");
sb.append("}");
sb.append("}");
return sb.toString();
}
/**
* 分账结果类包含分账是否成功以及失败原因
*/
public static class SharingResult {
private boolean success;
private String errorMessage;
public SharingResult() {
}
public SharingResult(boolean success, String errorMessage) {
this.success = success;
this.errorMessage = errorMessage;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
@Override
public String toString() {
return success ? "分账成功" : "分账失败: " + errorMessage;
}
}
/**
* 分账计算结果类包含分账计算的详细结果
*/
public static class SharingCalculationResult extends SharingResult {
// 分账金额结果属性
private Integer lklAmount; // 拉卡拉分账金额()
private Integer canSeparateAmount; // 可分账金额()
private Integer refCanSeparateAmount; // 拉卡拉参考可分账金额()
private Integer shippingFee; // 配送费()
private Integer platAmount; // 平台分账金额()
private Integer agent1stAmount; // 一级代理商分账金额()
private Integer agent2ndAmount; // 二级代理商分账金额()
private Integer mchAmount; // 商户分账金额()
public SharingCalculationResult() {
}
public SharingCalculationResult(boolean success, String errorMessage) {
super(success, errorMessage);
}
// Getters and Setters
public Integer getLklAmount() {
return lklAmount;
}
public void setLklAmount(Integer lklAmount) {
this.lklAmount = lklAmount;
}
public Integer getCanSeparateAmount() {
return canSeparateAmount;
}
public void setCanSeparateAmount(Integer canSeparateAmount) {
this.canSeparateAmount = canSeparateAmount;
}
public Integer getRefCanSeparateAmount() {
return refCanSeparateAmount;
}
public void setRefCanSeparateAmount(Integer refCanSeparateAmount) {
this.refCanSeparateAmount = refCanSeparateAmount;
}
public Integer getShippingFee() {
return shippingFee;
}
public void setShippingFee(Integer shippingFee) {
this.shippingFee = shippingFee;
}
public Integer getPlatAmount() {
return platAmount;
}
public void setPlatAmount(Integer platAmount) {
this.platAmount = platAmount;
}
public Integer getAgent1stAmount() {
return agent1stAmount;
}
public void setAgent1stAmount(Integer agent1stAmount) {
this.agent1stAmount = agent1stAmount;
}
public Integer getAgent2ndAmount() {
return agent2ndAmount;
}
public void setAgent2ndAmount(Integer agent2ndAmount) {
this.agent2ndAmount = agent2ndAmount;
}
public Integer getMchAmount() {
return mchAmount;
}
public void setMchAmount(Integer mchAmount) {
this.mchAmount = mchAmount;
}
}
}

View File

@ -8,7 +8,12 @@
package com.suisung.mall.common.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -140,6 +145,117 @@ public class CommonService {
}
public static void main(String[] args) {
System.out.println(isValidInput("", "+8618924071446"));
System.out.println(getLklCombineSplitRespInfo("8226330541100GU", "[{\"sub_trade_no\":\"20250911110110000066202050017882\",\"sub_log_no\":\"66202050017882\",\"origin_sub_trade_no\":\"20250911110113130266250044162421\",\"origin_sub_log_no\":\"66250044162421\",\"merchant_no\":\"822584059990FYP\",\"term_no\":\"N5811590\",\"amount\":\"1\"},{\"sub_trade_no\":\"20250911110110000066202050152097\",\"sub_log_no\":\"66202050152097\",\"origin_sub_trade_no\":\"20250911110113130266250044162422\",\"origin_sub_log_no\":\"66250044162422\",\"merchant_no\":\"8226330541100GU\",\"term_no\":\"N5817779\",\"amount\":\"1\"}]", false));
}
/**
* 从拉卡拉分账响应信息中提取合单运费或商品订单信息
* <p>该方法用于处理合单支付场景从拉卡拉返回的分账信息中筛选出运费子单或商品子单信息</p>
*
* @param merchantNo 商家商户号
* @param outSplitRspInfos 拉卡拉分账响应信息格式为JSON数组字符串
* [
* {
* "sub_trade_no":"20250830110113130266250034401288", // 子交易流水号
* "merchant_no":"822584059990FYP", // 商户号
* "amount":"1", // 分账金额
* "settle_type":"0", // 结算类型
* "sub_log_no":"66250034401288", // 子流水号
* "out_sub_trade_no":"DF-DD-20250830-21", // 外部子交易订单号(DF开头为运费订单)
* "term_no":"N5811590" // 终端设备号
* },
* {
* "sub_trade_no":"20250830110113130266250034401289", // 子交易流水号
* "merchant_no":"8226330541100GU", // 商户号
* "amount":"1", // 分账金额
* "settle_type":"0", // 结算类型
* "sub_log_no":"66250034401289", // 子流水号
* "out_sub_trade_no":"ORD-DD-20250830-21", // 外部子交易订单号(ORD开头为商品订单)
* "term_no":"N5817779" // 终端设备号
* }
* ]
* @param isDeliveryFee true: 提取运费子单信息, false: 提取商品子单信息
* @return JSONObject 返回匹配的子单信息格式如下
* {
* "sub_trade_no":"20250830110113130266250034401288",
* "merchant_no":"822584059990FYP",
* "amount":"1",
* "settle_type":"0",
* "sub_log_no":"66250034401288",
* "out_sub_trade_no":"DF-DD-20250830-21",
* "term_no":"N5811590"
* }
*/
public static JSONObject getLklCombineSplitRespInfo(String merchantNo, String outSplitRspInfos, boolean isDeliveryFee) {
log.debug("[拉卡拉合单数据拆分] 开始获取合单数据: merchantNo={} isDeliveryFee={} outSplitRspInfos长度={}",
merchantNo, isDeliveryFee, outSplitRspInfos != null ? outSplitRspInfos.length() : 0);
// 输入参数校验检查分账信息是否为空
if (StrUtil.isBlank(outSplitRspInfos)) {
log.warn("[拉卡拉合单数据拆分] 合单数据为空");
return null;
}
// 解析JSON数组将字符串转换为JSONArray对象
JSONArray outSplitRspInfoArray;
try {
outSplitRspInfoArray = JSONUtil.parseArray(outSplitRspInfos);
log.debug("[拉卡拉合单数据拆分] JSON解析完成数组大小={}", outSplitRspInfoArray.size());
} catch (Exception e) {
log.error("[拉卡拉合单数据拆分] JSON解析失败{}", outSplitRspInfos, e);
return null;
}
// 检查解析后的数组是否为空
if (outSplitRspInfoArray.isEmpty()) {
log.warn("[拉卡拉合单数据拆分] 拆分合单数据失败:分账信息数组为空");
return null;
}
// 遍历JSON数组查找匹配的子单 (合单订单通常只有两个子单一个运费单一个商品单)
final String deliveryPrefix = CommonConstant.Sep_DeliveryFee_Prefix;
final String goodsPrefix = CommonConstant.Sep_GoodsFee_Prefix;
log.debug("[拉卡拉合单数据拆分] 开始遍历数组查找匹配子单: deliveryPrefix={} goodsPrefix={}", deliveryPrefix, goodsPrefix);
for (int i = 0; i < outSplitRspInfoArray.size(); i++) {
JSONObject item = outSplitRspInfoArray.getJSONObject(i);
if (item == null) {
log.debug("[拉卡拉合单数据拆分] 数组第{}项为空,跳过", i);
continue;
}
// 获取外部子交易订单号字段和商户号
String subMerchantNo = item.getStr("merchant_no");
String outSubTradeNo = item.getStr("out_sub_trade_no");
log.debug("[拉卡拉合单数据拆分] 检查第{}项: outSubTradeNo={} subMerchantNo={}", i, outSubTradeNo, subMerchantNo);
// 根据isDeliveryFee参数和商户号筛选对应的子单信息
if (isDeliveryFee && StrUtil.isNotBlank(merchantNo) && !merchantNo.equals(subMerchantNo)) {
log.debug("[拉卡拉合单数据拆分] 找到运费子单(通过商户号匹配): outSubTradeNo={} subMerchantNo={}", outSubTradeNo, subMerchantNo);
return item;
} else if (!isDeliveryFee && StrUtil.isNotBlank(merchantNo) && merchantNo.equals(subMerchantNo)) {
log.debug("[拉卡拉合单数据拆分] 找到商品子单(通过商户号匹配): outSubTradeNo={} subMerchantNo={}", outSubTradeNo, subMerchantNo);
return item;
} else {
// 如果商户号匹配失败则通过订单号前缀匹配
if (isDeliveryFee && StrUtil.isNotBlank(outSubTradeNo) && outSubTradeNo.startsWith(deliveryPrefix)) {
log.debug("[拉卡拉合单数据拆分] 找到运费子单(通过前缀匹配): outSubTradeNo={}", outSubTradeNo);
return item;
} else if (!isDeliveryFee && StrUtil.isNotBlank(outSubTradeNo) && outSubTradeNo.startsWith(goodsPrefix)) {
log.debug("[拉卡拉合单数据拆分] 找到商品子单(通过前缀匹配): outSubTradeNo={}", outSubTradeNo);
return item;
} else {
log.debug("[拉卡拉合单数据拆分] 第{}项不匹配条件: isDeliveryFee={} merchantNo={} subMerchantNo={} outSubTradeNo={}",
i, isDeliveryFee, merchantNo, subMerchantNo, outSubTradeNo);
}
}
}
// 未找到匹配的子单信息
log.warn("[拉卡拉合单数据拆分] 未找到匹配的子单信息: merchantNo={} isDeliveryFee={}", merchantNo, isDeliveryFee);
return null;
}
}

View File

@ -182,7 +182,7 @@ public class UniCloudPushServiceImpl implements UniCloudPushService {
try {
JSONObject json = JSONUtil.parseObj(body);
log.info("[推送服务] 原始响应: {}", json.toStringPretty());
// log.debug("[推送服务] 原始响应: {}", json.toStringPretty());
// 解析标准响应字段
Integer errCode = json.getInt("errCode", -1);

View File

@ -356,13 +356,104 @@ public class CommonUtil {
/**
* 对象去重工具
*
* @param keyExtractor
* @return
* @param <T>
* @return
*/
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = new HashSet<>();
return t -> seen.add(keyExtractor.apply(t));
}
/**
* 按照指定比例计算分账金额
* 分配优先级: 商家 > 平台 > 代理商
*
* @param totalAmount 分账总金额(单位:)
* @param merchantRatio 商家分账比例
* @param platformRatioInRemaining 平台在剩余比例中的分账比例
* @param agentRatioInRemaining 代理商在剩余比例中的分账比例
* @return 包含商家平台代理商分得金额的Map
*/
public static Map<String, Integer> calculateProfitSharing(int totalAmount,
BigDecimal merchantRatio,
BigDecimal platformRatioInRemaining,
BigDecimal agentRatioInRemaining) {
Map<String, Integer> result = new HashMap<>();
// 初始化各方法定金额
int merchantAmount = 0;
int platformAmount = 0;
int agentAmount = 0;
// 1. 计算商家按比例应得的金额(优先级最高)
if (merchantRatio != null && merchantRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal merchantAmountDecimal = merchantRatio.multiply(new BigDecimal(totalAmount));
merchantAmount = merchantAmountDecimal.setScale(0, RoundingMode.DOWN).intValue();
// 确保商家至少获得1分钱(如果商家参与分账但分配为0且总金额大于0)
if (merchantAmount == 0 && totalAmount > 0) {
merchantAmount = 1;
}
}
// 2. 计算剩余金额
int remainingAmount = totalAmount - merchantAmount;
// 3. 只有当有剩余金额时才计算平台和代理商的分配
if (remainingAmount > 0) {
// 计算剩余比例(1 - 商家比例)
BigDecimal remainingRatio = new BigDecimal("1");
if (merchantRatio != null && merchantRatio.compareTo(BigDecimal.ZERO) > 0) {
remainingRatio = new BigDecimal("1").subtract(merchantRatio);
}
// 如果代理商不参与分账
if (agentRatioInRemaining == null || agentRatioInRemaining.compareTo(BigDecimal.ZERO) <= 0) {
// 所有剩余金额都给平台
platformAmount = remainingAmount;
} else {
// 计算平台应得金额基于剩余金额
if (platformRatioInRemaining != null && platformRatioInRemaining.compareTo(BigDecimal.ZERO) > 0) {
// 平台在剩余部分中的分配 = 剩余金额 * 平台在剩余中的比例
BigDecimal platformShareOfRemaining = platformRatioInRemaining.multiply(new BigDecimal(remainingAmount));
platformAmount = platformShareOfRemaining.setScale(0, RoundingMode.DOWN).intValue();
}
// 计算代理商应得金额基于剩余金额
if (agentRatioInRemaining != null && agentRatioInRemaining.compareTo(BigDecimal.ZERO) > 0) {
// 代理商在剩余部分中的分配 = 剩余金额 * 代理商在剩余中的比例
BigDecimal agentShareOfRemaining = agentRatioInRemaining.multiply(new BigDecimal(remainingAmount));
agentAmount = agentShareOfRemaining.setScale(0, RoundingMode.DOWN).intValue();
}
// 重新计算剩余金额用于最终分配
int finalRemainingAmount = remainingAmount - platformAmount - agentAmount;
// 确保平台至少获得1分钱(如果平台参与分账但分配为0且还有剩余金额)
if (platformRatioInRemaining != null && platformRatioInRemaining.compareTo(BigDecimal.ZERO) > 0
&& platformAmount == 0 && finalRemainingAmount > 0) {
platformAmount = 1;
finalRemainingAmount--;
}
// 按优先级分配剩余金额: 商家 > 平台 > 代理商
// 剩余金额给商家因为商家优先级最高
merchantAmount += finalRemainingAmount;
}
}
result.put("merchantAmount", merchantAmount);
result.put("platformAmount", platformAmount);
result.put("agentAmount", agentAmount);
return result;
}
public static void main(String[] args) {
System.out.println("测试1分钱分配:");
System.out.println(calculateProfitSharing(9800, new BigDecimal("0.94"), new BigDecimal("0.2"), new BigDecimal("0")));
}
}

View File

@ -6,12 +6,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class VideoUtil {
private static final Logger logger = LoggerFactory.getLogger(VideoUtil.class);
public static List<String> videoAllowFiles = new ArrayList<String>() {{
add("flv");
add("swf");
@ -31,7 +33,6 @@ public class VideoUtil {
add("wav");
add("mid");
}};
private static Logger logger = LoggerFactory.getLogger(VideoUtil.class);
/**
* 得到语音或视频文件时长,单位秒 并格式化
@ -51,11 +52,43 @@ public class VideoUtil {
* @return 单位为毫秒
*/
public static long getMp4Duration(String videoPath) throws IOException {
IsoFile isoFile = new IsoFile(videoPath);
long lengthInSeconds =
isoFile.getMovieBox().getMovieHeaderBox().getDuration() /
isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
return lengthInSeconds;
try {
IsoFile isoFile = new IsoFile(videoPath);
long lengthInSeconds =
isoFile.getMovieBox().getMovieHeaderBox().getDuration() /
isoFile.getMovieBox().getMovieHeaderBox().getTimescale();
return lengthInSeconds;
} catch (Exception e) {
// 处理 MOV 文件可能存在的解析问题尝试使用 FFmpeg 获取时长
logger.warn("无法通过 IsoFile 解析视频文件时长: {}, 尝试使用 FFmpeg", videoPath);
try {
return getDurationWithFFmpeg(videoPath);
} catch (Exception ffmpegException) {
logger.error("FFmpeg 也无法解析视频文件时长: {}", videoPath);
throw new IOException("无法解析视频文件: " + videoPath, e);
}
}
}
/**
* 使用 FFmpeg 获取视频时长备用方法
*
* @param videoPath
* @return 单位为毫秒
* @throws IOException
* @throws InterruptedException
*/
private static long getDurationWithFFmpeg(String videoPath) throws IOException, InterruptedException {
String command = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 " + videoPath;
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
process.waitFor();
if (result != null) {
// ffprobe 返回的是秒数需要转换为毫秒
return (long) (Double.parseDouble(result) * 1000);
}
throw new IOException("无法获取视频时长");
}
@ -194,8 +227,7 @@ public class VideoUtil {
int mm = (temp % 3600) / 60;
int ss = (temp % 3600) % 60;
return hh != 0 ? ((hh < 10 ? ("0" + hh) : hh) + ":") : "" +
(mm < 10 ? ("0" + mm) : mm) + ":" +
return hh != 0 ? ((hh < 10 ? ("0" + hh) : hh) + ":") : (mm < 10 ? ("0" + mm) : mm) + ":" +
(ss < 10 ? ("0" + ss) : ss);
}
}
@ -206,7 +238,7 @@ class InputStreamRunnable extends Thread {
public InputStreamRunnable(InputStream is, String _type) {
try {
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), "UTF-8"));
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), StandardCharsets.UTF_8));
type = _type;
} catch (Exception ex) {
ex.printStackTrace();

View File

@ -90,6 +90,7 @@ secure:
- "/esProduct/**"
- "/admin/oss/upload/**"
- "/admin/shop/wxqrcode/common/wxurlscheme"
- "/mobile/shop/lakala/sign/ec/**"
- "/mobile/**/**/test/case"
- "/**/**/testcase"
universal:

View File

@ -24,44 +24,10 @@ import java.io.IOException;
@RestController
@RequestMapping("/mobile/pay/lakala")
public class LakalaController extends BaseControllerImpl {
//
// @Resource
// private LakalaPayService lakalaPayService;
@ApiOperation(value = "本地文件转base64", notes = "本地文件转base64")
@RequestMapping(value = "/file2base64", method = RequestMethod.POST)
public String file2Base64(@RequestParam("file") MultipartFile file) throws IOException {
String str = Base64Utils.encodeToString(file.getBytes());
return str;
}
// @ApiOperation(value = "商户分账业务开通申请", notes = "商户分账业务开通申请")
// @RequestMapping(value = "/ledger/applyLedgerMer", method = RequestMethod.POST)
// public CommonResult ledgerApplyLedgerMer(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerMer(paramsJSON);
// }
//
// @ApiOperation(value = "商户分账业务开通申请异步回调回调", notes = "商户分账业务开通申请异步回调回调")
// @RequestMapping(value = "/ledger/applyLedgerMerNotify", method = RequestMethod.POST)
// public JSONObject ledgerApplyLedgerMerNotify(HttpServletRequest request) {
// return lakalaPayService.applyLedgerMerNotify(request);
// }
//
// @ApiOperation(value = "分账接收方创建申请", notes = "分账接收方创建申请")
// @RequestMapping(value = "/ledger/applyLedgerReceiver", method = RequestMethod.POST)
// public CommonResult applyLedgerReceiver(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerReceiver(paramsJSON);
// }
//
// @ApiOperation(value = "分账关系绑定申请", notes = "分账关系绑定申请")
// @RequestMapping(value = "/ledger/applyBind", method = RequestMethod.POST)
// public CommonResult applyBind(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerMerReceiverBind(paramsJSON);
// }
//
// @ApiOperation(value = "分账关系绑定申请异步回调通知", notes = "分账关系绑定申请异步回调通知")
// @RequestMapping(value = "/ledger/applyBindNotify", method = RequestMethod.POST)
// public JSONObject applyBindNotify(HttpServletRequest request) {
// return lakalaPayService.applyLedgerMerReceiverBindNotify(request);
// }
}

View File

@ -39,4 +39,19 @@ public interface AccountBaseConfigService extends IBaseService<AccountBaseConfig
boolean getTradeModePlantform();
/**
* 获取系统配置
*
* @param configKey
* @return
*/
String getSystemConfig(String configKey);
/**
* 获取平台内部最低配送费单位
*
* @return
*/
Integer getInnerMinDeliveryFee();
}

View File

@ -7,6 +7,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.constant.RedisConstant;
import com.suisung.mall.common.feignService.AccountService;
import com.suisung.mall.common.modules.account.AccountBaseConfig;
@ -226,6 +227,43 @@ public class AccountBaseConfigServiceImpl extends BaseServiceImpl<AccountBaseCon
return getTradeMode();
}
/**
* 获取系统配置值
* <p>
* 根据配置键获取对应的配置值如果配置值为空则返回空字符串
* </p>
*
* @param configKey 配置键
* @return 配置值如果找不到或为空则返回空字符串
*/
@Override
public String getSystemConfig(String configKey) {
// 参数校验
if (StrUtil.isBlank(configKey)) {
log.warn("[系统配置] 参数校验失败:配置键不能为空");
return "";
}
try {
String configValue = accountService.getAccountBaseConfigValue(configKey);
return StrUtil.blankToDefault(configValue, "0");
} catch (Exception e) {
log.error("[系统配置] 获取配置值异常configKey={}", configKey, e);
return "";
}
}
/**
* 获取平台内部最低配送费单位
*
* @return
*/
@Override
public Integer getInnerMinDeliveryFee() {
String v = getSystemConfig(CommonConstant.Inner_Min_DeliveryFee_Key);
return NumberUtil.isNumber(v) ? Convert.toInt(v) : 0;
}
/**
* 交易模式 2直接交易:商家直接收款 1担保交易平台收款平台和商家结算
*/

View File

@ -83,7 +83,7 @@ public class AnalytiscTradeServiceImpl implements AnalytiscTradeService {
BigDecimal daym2m = BigDecimal.ZERO;
if (yestodayTradeAmount.getAmount().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (todayTradeAmount.getAmount().subtract(yestodayTradeAmount.getAmount())).divide(yestodayTradeAmount.getAmount(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal("100"));
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -20,19 +20,16 @@ import com.lkl.laop.sdk.LKLSDK;
import com.lkl.laop.sdk.exception.SDKException;
import com.lkl.laop.sdk.request.V2MmsOpenApiUploadFileRequest;
import com.lkl.laop.sdk.request.V3LabsRelationRefundRequest;
import com.lkl.laop.sdk.request.V3LabsTransPreorderRequest;
import com.lkl.laop.sdk.request.model.V3LabsTradeLocationInfo;
import com.lkl.laop.sdk.request.model.V3LabsTradePreorderWechatBus;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.feignService.ShopService;
import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.RestTemplateHttpUtil;
import com.suisung.mall.pay.service.AccountBaseConfigService;
import com.suisung.mall.pay.service.LakalaPayService;
import com.suisung.mall.pay.service.LklLedgerMemberService;
import com.suisung.mall.pay.service.LklLedgerMerReceiverBindService;
import com.suisung.mall.pay.service.LklLedgerReceiverService;
import com.suisung.mall.pay.utils.LakalaUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -82,14 +79,9 @@ public class LakalaPayServiceImpl implements LakalaPayService {
@Autowired
private ShopService shopService;
@Lazy
@Autowired
private LklLedgerMemberService lklLedgerMemberService;
@Autowired
private LklLedgerReceiverService lklLedgerReceiverService;
@Autowired
private LklLedgerMerReceiverBindService lklLedgerMerReceiverBindService;
private AccountBaseConfigService accountBaseConfigService;
/**
* 初始化 拉卡拉SDK
@ -120,76 +112,170 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param storeId 店铺号
* @param orderId 订单号
* @param subject 订单标题
* @param totalAmount 订单金额
* @param totalAmount 订单金额单位
* @param notifyURL 回调地址
* @param requestIP 请求ip
* @param requestIP 请求IP
* @param remark 备注
* @return
* @return 拉卡拉预下单响应结果
*/
@Override
public JSONObject lklTransPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) {
// 1. 配置初始化
initLKLSDK();
if (StrUtil.isBlank(merchantNo) || StrUtil.isBlank(termNo)) {
throw new ApiException(I18nUtil._("缺少商户号或终端号!"));
// 1. 参数校验
if (StrUtil.isBlank(merchantNo)) {
log.warn("[拉卡拉预下单] 参数校验失败:商户号不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("商户号不能为空!"));
}
//2. 装配数据
/*** 微信主扫场景示例 */
V3LabsTransPreorderRequest v3LabsTransPreorderWechatReq = new V3LabsTransPreorderRequest();
v3LabsTransPreorderWechatReq.setMerchantNo(merchantNo);
v3LabsTransPreorderWechatReq.setTermNo(termNo);
v3LabsTransPreorderWechatReq.setOutTradeNo(orderId);
v3LabsTransPreorderWechatReq.setSubject(subject);
//微信WECHAT 支付宝ALIPAY 银联UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户LKLACC 网联小钱包NUCSPAY 京东钱包JD
v3LabsTransPreorderWechatReq.setAccountType("WECHAT");
// 41:NATIVEALIPAY云闪付支持京东白条分期51:JSAPI微信公众号支付支付宝服务窗支付银联JS支付翼支付JS支付拉卡拉钱包支付71:微信小程序支付 61:APP支付微信APP支付
v3LabsTransPreorderWechatReq.setTransType("51");
v3LabsTransPreorderWechatReq.setTotalAmount(totalAmount); // 应支付金额单位
v3LabsTransPreorderWechatReq.setSettleType("1"); //0或者空常规结算方式如需接拉卡拉分账通需传1商户未开通分账之前切记不用上送此参数
v3LabsTransPreorderWechatReq.setNotifyUrl(notifyURL);
v3LabsTransPreorderWechatReq.setRemark(remark);
if (StrUtil.isBlank(termNo)) {
log.warn("[拉卡拉预下单] 参数校验失败:终端号不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("终端号不能为空!"));
}
//地址位置信息
V3LabsTradeLocationInfo v3LabsTradePreorderLocationInfo = new V3LabsTradeLocationInfo(requestIP);
v3LabsTransPreorderWechatReq.setLocationInfo(v3LabsTradePreorderLocationInfo);
if (StrUtil.isBlank(orderId)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单号不能为空, merchantNo={}", merchantNo);
throw new ApiException(I18nUtil._("订单号不能为空!"));
}
//微信主扫场景下 acc_busi_fields 域内容
V3LabsTradePreorderWechatBus wechatBus = new V3LabsTradePreorderWechatBus();
wechatBus.setSubAppid(xcxAppId); // 小程序appId
wechatBus.setUserId(openId); // 微信 openId
wechatBus.setDeviceInfo("WEB"); // 终端设备号(门店号或收银设备ID)注意PC网页或JSAPI支付请传WEB
// wechatBus.setAttach(storeId); // 附加数据商户自定义数据在查询交易结果时原样返回
v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus);
if (StrUtil.isBlank(subject)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单标题不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("订单标题不能为空!"));
}
if (StrUtil.isBlank(totalAmount)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单金额不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("订单金额不能为空!"));
}
if (StrUtil.isBlank(notifyURL)) {
log.warn("[拉卡拉预下单] 参数校验失败:回调地址不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("回调地址不能为空!"));
}
if (StrUtil.isBlank(requestIP)) {
log.warn("[拉卡拉预下单] 参数校验失败请求IP不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("请求IP不能为空"));
}
log.info("[拉卡拉预下单] 开始处理请求, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId);
try {
log.info("拉卡拉预下单请求参数:{}", JSONUtil.toJsonStr(v3LabsTransPreorderWechatReq));
// 2. 装配请求数据
JSONObject reqData = new JSONObject();
reqData.put("merchant_no", merchantNo);
reqData.put("term_no", termNo);
reqData.put("out_trade_no", orderId);
reqData.put("subject", subject);
// 微信WECHAT 支付宝ALIPAY 银联UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户LKLACC 网联小钱包NUCSPAY 京东钱包JD
reqData.put("account_type", "WECHAT");
// 41:NATIVEALIPAY云闪付支持京东白条分期51:JSAPI微信公众号支付支付宝服务窗支付银联JS支付翼支付JS支付拉卡拉钱包支付71:微信小程序支付 61:APP支付微信APP支付
reqData.put("trans_type", "51");
reqData.put("total_amount", totalAmount); // 应支付金额单位
reqData.put("settle_type", "1"); // "0"或者空常规结算方式如需接拉卡拉分账通需传"1"商户未开通分账之前切记不用上送此参数
reqData.put("notify_url", notifyURL);
reqData.put("remark", remark);
reqData.put("complete_notify_url", "https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify");
//3. 发送请求
String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq);
log.info("拉卡拉预下单响应数据:{}", responseStr);
if (StrUtil.isBlank(responseStr)) {
// 地址位置信息
JSONObject locationInfo = new JSONObject();
locationInfo.put("request_ip", requestIP);
reqData.put("location_info", locationInfo);
// 微信业务参数
if (StrUtil.isNotBlank(xcxAppId) && StrUtil.isNotBlank(openId)) {
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appId
accBusiFields.put("user_id", openId); // 微信 openId
accBusiFields.put("device_info", "WEB"); // 终端设备号(门店号或收银设备ID)注意PC网页或JSAPI支付请传"WEB"
reqData.put("acc_busi_fields", accBusiFields);
log.debug("[拉卡拉预下单] 已添加微信业务参数, xcxAppId={}, openId={}", xcxAppId, openId);
} else {
log.warn("[拉卡拉预下单] 微信业务参数不完整或不需要, xcxAppId={}, openId={}", xcxAppId, openId);
}
// 3. 构造请求体
JSONObject reqBody = new JSONObject();
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
reqBody.put("version", "3.0");
reqBody.put("req_data", reqData);
log.info("[拉卡拉预下单] 请求参数组装完成, orderId={}", orderId);
log.debug("[拉卡拉预下单] 完整请求参数: {}", JSONUtil.toJsonStr(reqBody));
// 4. 发送请求
String reqUrl = serverUrl + "/api/v3/labs/trans/preorder";
log.info("[拉卡拉预下单] 准备发送请求, orderId={}, url={}", orderId, reqUrl);
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
if (StrUtil.isBlank(authorization)) {
log.error("[拉卡拉预下单] 生成签名失败, orderId={}", orderId);
throw new ApiException("生成请求签名失败!");
}
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
header.put("Content-Type", "application/json");
log.debug("[拉卡拉预下单] 请求头信息生成完成, orderId={}", orderId);
// 使用RestTemplate发送POST请求
ResponseEntity<JSONObject> lakalaRespEntity = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("[拉卡拉预下单] 收到响应, orderId={}, responseStatus={}", orderId, lakalaRespEntity != null ? lakalaRespEntity.getStatusCode() : "NULL");
// 5. 处理响应结果
if (lakalaRespEntity == null) {
log.warn("[拉卡拉预下单] 响应为空, orderId={}", orderId);
return null;
}
JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
JSONObject respBody = lakalaRespEntity.getBody();
log.debug("[拉卡拉预下单] 响应体内容, orderId={}, responseBody={}", orderId, respBody);
if (lakalaRespJSON != null && lakalaRespJSON.getStr("code").equals("BBS00000")) {
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", JSONUtil.parseObj(v3LabsTransPreorderWechatReq));
lklPayReqAndRespJson.put("resp", lakalaRespJSON);
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
if (respBody == null) {
log.warn("[拉卡拉预下单] 响应体为空, orderId={}", orderId);
return null;
}
//4. 响应
return lakalaRespJSON;
} catch (SDKException e) {
log.error("拉卡拉支付出错:", e);
throw new ApiException(I18nUtil._("支付失败!"), e);
String responseCode = respBody.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.warn("[拉卡拉预下单] 响应码为空, orderId={}", orderId);
return respBody;
}
// 使用安全的字符串比较方式避免空指针异常
if (!lklPaySuccessCode.equals(responseCode)) {
log.warn("[拉卡拉预下单] 响应码异常, orderId={}, code={}, msg={}", orderId, responseCode, respBody.getStr("msg"));
return respBody;
}
// 平台最低配送费单位
Integer innerMinDeliverFee = accountBaseConfigService.getInnerMinDeliveryFee();
reqData.set("shopping_fee_inner", innerMinDeliverFee); // 平台内部最低配送费单位
log.info("[拉卡拉预下单] 支付成功,准备保存订单记录, orderId={}", orderId);
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", reqData);
lklPayReqAndRespJson.put("resp", respBody);
try {
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
log.debug("[拉卡拉预下单] 订单记录保存成功, orderId={}", orderId);
} catch (Exception e) {
log.error("[拉卡拉预下单] 保存订单记录失败, orderId={}", orderId, e);
// 不中断主流程仅记录错误
}
// 6. 返回响应结果
log.info("[拉卡拉预下单] 处理完成, orderId={}, responseCode={}", orderId, responseCode);
return respBody;
} catch (Exception e) {
log.error("[拉卡拉预下单] 系统异常, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId, e);
throw new ApiException("拉卡拉预下单出错:" + e.getMessage(), e);
}
}
/**
* 拉卡拉合单预下单主要有运费的订单使用合单
* 参考https://o.lakala.com/#/home/document/detail?id=208
@ -203,41 +289,64 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param storeId 店铺Id
* @param orderId 订单号
* @param subject 订单标题
* @param totalAmount 订单总金额
* @param agentAmount 代理商收取金额
* @param totalAmount 订单总金额单位
* @param agentAmount 代理商收取金额单位
* @param notifyURL 回调地址
* @param requestIP 请求ip
* @param requestIP 请求IP
* @param remark 备注
* @return 拉卡拉合单预下单响应结果
**/
@Override
public JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String agentTermNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark) {
// 2. 参数校验
log.info("[拉卡拉合单预下单] 开始处理请求, merchantNo={}, termNo={}, agentMerchantNo={}, agentTermNo={}, orderId={}",
merchantNo, termNo, agentMerchantNo, agentTermNo, orderId);
// 1. 参数校验
if (StrUtil.isBlank(merchantNo)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:商家商户号不能为空, orderId={}", orderId);
throw new ApiException("商家商户号不能为空!");
}
if (StrUtil.isBlank(termNo)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:终端号不能为空, orderId={}", orderId);
throw new ApiException("终端号不能为空!");
}
// 3. 校验其他必要参数
if (StrUtil.isBlank(orderId)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单号不能为空, merchantNo={}, termNo={}", merchantNo, termNo);
throw new ApiException("订单号不能为空!");
}
if (StrUtil.isBlank(subject)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单标题不能为空, orderId={}", orderId);
throw new ApiException("订单标题不能为空!");
}
if (StrUtil.isBlank(totalAmount)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单总金额不能为空, orderId={}", orderId);
throw new ApiException("订单总金额不能为空!");
}
if (StrUtil.isBlank(notifyURL)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:回调地址不能为空, orderId={}", orderId);
throw new ApiException("回调地址不能为空!");
}
if (StrUtil.isBlank(requestIP)) {
log.warn("[拉卡拉合单预下单] 参数校验失败请求IP不能为空, orderId={}", orderId);
throw new ApiException("请求IP不能为空");
}
// 如果不符合合单交易条件则走聚合主扫单笔交易
if (StrUtil.isBlank(agentMerchantNo) || StrUtil.isBlank(agentTermNo) || StrUtil.isBlank(agentAmount) || "0".equals(agentAmount)) {
log.info("不符合合单交易条件,转为单笔交易处理。商家商户号:{},代理商商户号:{},代理商终端号:{},代理商金额:{}",
log.info("[拉卡拉合单预下单] 不符合合单交易条件,转为单笔交易处理。商家商户号:{},代理商商户号:{},代理商终端号:{},代理商金额:{}",
merchantNo, agentMerchantNo, agentTermNo, agentAmount);
return lklTransPreOrder(merchantNo, termNo, xcxAppId, openId, storeId, orderId, subject, totalAmount, notifyURL, requestIP, remark);
}
try {
// 4. 装配请求数据
log.debug("[拉卡拉合单预下单] 开始装配请求数据, orderId={}", orderId);
// 2. 装配请求数据
JSONObject reqData = new JSONObject();
// 基本交易信息
@ -250,6 +359,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
reqData.put("subject", subject); // 订单标题
reqData.put("notify_url", notifyURL); // 异步通知地址
reqData.put("remark", remark); // 备注
reqData.put("complete_notify_url", "https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify"); // 发货类小程序确认收获后通知商户的地址
// 位置信息
JSONObject locationInfo = new JSONObject();
@ -257,24 +367,42 @@ public class LakalaPayServiceImpl implements LakalaPayService {
reqData.put("location_info", locationInfo);
// 微信业务参数
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appid
accBusiFields.put("user_id", openId); // 用户openid
reqData.put("acc_busi_fields", accBusiFields);
if (StrUtil.isNotBlank(xcxAppId) && StrUtil.isNotBlank(openId)) {
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appId
accBusiFields.put("user_id", openId); // 用户openid
reqData.put("acc_busi_fields", accBusiFields);
log.debug("[拉卡拉合单预下单] 已添加微信业务参数, xcxAppId={}, openId={}", xcxAppId, openId);
} else {
log.warn("[拉卡拉合单预下单] 微信业务参数不完整或不需要, xcxAppId={}, openId={}", xcxAppId, openId);
}
// 重要约定订单号规则商品订单ORD-订单号,运费订单DF-订单号
// 分单信息
// 重要约定订单号规则商品订单ORD_订单号,运费订单DF_订单号
// 商品子单信息
JSONObject goodsSplitInfo = new JSONObject();
goodsSplitInfo.put("out_sub_trade_no", "ORD-" + orderId); // 子订单号
goodsSplitInfo.put("out_sub_trade_no", CommonConstant.Sep_GoodsFee_Prefix + orderId); // 商品子订单号
goodsSplitInfo.put("merchant_no", merchantNo); // 分账商户号
goodsSplitInfo.put("term_no", termNo); // 分账终端号
int totalAmountInt = Convert.toInt(totalAmount) - Convert.toInt(agentAmount);
goodsSplitInfo.put("amount", Convert.toStr(totalAmountInt)); // 分账金额
goodsSplitInfo.put("settle_type", "0"); // "0"或者空常规结算方式
// 安全转换金额避免类型转换异常
int totalAmountInt = 0;
int agentAmountInt = 0;
try {
totalAmountInt = Convert.toInt(totalAmount);
agentAmountInt = Convert.toInt(agentAmount);
} catch (NumberFormatException e) {
log.error("[拉卡拉合单预下单] 金额转换异常, totalAmount={}, agentAmount={}, orderId={}", totalAmount, agentAmount, orderId, e);
throw new ApiException("金额格式错误!");
}
int goodsAmountInt = totalAmountInt - agentAmountInt;
goodsSplitInfo.put("amount", Convert.toStr(goodsAmountInt)); // 分账金额
goodsSplitInfo.put("settle_type", "1"); // "0"或者空常规结算方式
goodsSplitInfo.put("sub_remark", "商品订单金额"); // 子单备注信息
// 运费子单信息
JSONObject deliverySplitInfo = new JSONObject();
deliverySplitInfo.put("out_sub_trade_no", "DF-" + orderId); // 子订单号
deliverySplitInfo.put("out_sub_trade_no", CommonConstant.Sep_DeliveryFee_Prefix + orderId); // 运费子订单号
deliverySplitInfo.put("merchant_no", agentMerchantNo); // 分账商户号
deliverySplitInfo.put("term_no", agentTermNo); // 分账终端号
deliverySplitInfo.put("amount", agentAmount); // 分账金额
@ -286,48 +414,89 @@ public class LakalaPayServiceImpl implements LakalaPayService {
outSplitInfo.add(goodsSplitInfo);
reqData.put("out_split_info", outSplitInfo);
// 5. 构造请求体
log.info("[拉卡拉合单预下单] 分单信息组装完成, orderId={}, goodsAmount={}分, deliveryAmount={}分",
orderId, goodsAmountInt, agentAmountInt);
// 3. 构造请求体
JSONObject reqBody = new JSONObject();
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
reqBody.put("version", "3.0");
reqBody.put("req_data", reqData);
// 6. 发送请求
log.info("[拉卡拉合单预下单] 请求参数组装完成, orderId={}", orderId);
log.debug("[拉卡拉合单预下单] 完整请求参数: {}", JSONUtil.toJsonStr(reqBody));
// 4. 发送请求
String reqUrl = serverUrl + "/api/v3/labs/trans/merge/preorder";
log.info("拉卡拉合单预下单请求参数:{}", reqBody);
log.info("[拉卡拉合单预下单] 准备发送请求, orderId={}, url={}", orderId, reqUrl);
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
if (StrUtil.isBlank(authorization)) {
log.error("[拉卡拉合单预下单] 生成签名失败, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "生成请求签名失败").set("resp_data", null);
}
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
header.put("Content-Type", "application/json");
// 这里的请求方法对返回的字段进行了处理需要注意
ResponseEntity<JSONObject> lakalaRespJSON = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("拉卡拉合单交易响应参数:{}", lakalaRespJSON);
log.debug("[拉卡拉合单预下单] 请求头信息生成完成, orderId={}", orderId);
// 7. 处理响应结果
if (lakalaRespJSON == null) {
// 发送请求
ResponseEntity<JSONObject> lakalaRespEntity = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("[拉卡拉合单预下单] 收到响应, orderId={}, responseStatus={}", orderId, lakalaRespEntity != null ? lakalaRespEntity.getStatusCode() : "NULL");
// 5. 处理响应结果
if (lakalaRespEntity == null) {
log.warn("[拉卡拉合单预下单] 响应为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易无响应值").set("resp_data", null);
}
JSONObject respBody = lakalaRespJSON.getBody();
JSONObject respBody = lakalaRespEntity.getBody();
log.debug("[拉卡拉合单预下单] 响应体内容, orderId={}, responseBody={}", orderId, respBody);
// 7. 处理响应结果
if (!lklPaySuccessCode.equals(respBody.getStr("code")) || respBody == null) {
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易无响应值").set("resp_data", null);
// 6. 处理响应结果
if (respBody == null) {
log.warn("[拉卡拉合单预下单] 响应体为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易响应体为空").set("resp_data", null);
}
String responseCode = respBody.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.warn("[拉卡拉合单预下单] 响应码为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易响应码为空").set("resp_data", null);
}
// 使用安全的字符串比较方式避免空指针异常
if (!lklPaySuccessCode.equals(responseCode)) {
log.warn("[拉卡拉合单预下单] 响应码异常, orderId={}, code={}, msg={}", orderId, responseCode, respBody.getStr("msg"));
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易失败:" + respBody.getStr("msg")).set("resp_data", null);
}
log.info("[拉卡拉合单预下单] 支付成功,准备保存订单记录, orderId={} shopping_fee_inner={}", orderId, agentAmountInt);
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
reqData.set("shopping_fee_inner", agentAmountInt); // 平台内部最低配送费单位
lklPayReqAndRespJson.put("req", reqData);
lklPayReqAndRespJson.put("resp", respBody); // 返回原始响应数据
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
// 8. 返回响应结果
try {
// 新增 shopOrderLkl 记录
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
log.debug("[拉卡拉合单预下单] 订单记录保存成功, orderId={}", orderId);
} catch (Exception e) {
log.error("[拉卡拉合单预下单] 保存订单记录失败, orderId={}", orderId, e);
// 不中断主流程仅记录错误
}
// 7. 返回响应结果
log.info("[拉卡拉合单预下单] 处理完成, orderId={}, responseCode={}", orderId, responseCode);
return respBody;
} catch (Exception e) {
log.error("拉卡拉合单交易出错,订单号:{},错误信息:", orderId, e);
throw new ApiException("拉卡拉合单交易出错:" + e.getMessage(), e);
log.error("[拉卡拉合单预下单] 系统异常, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId, e);
return new JSONObject().set("code", "BBS00001").set("msg", "系统异常" + e.getMessage()).set("resp_data", null);
}
}
@ -337,7 +506,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* 参考地址https://o.lakala.com/#/home/document/detail?id=113
*
* @param storeId 店铺ID
* @param outTradeNo 退货订单号 FX-20241214-1
* @param outTradeNo 外部交易订单号
* @param originTradeNo 原拉卡拉交易流水号
* @param refundAmount 退款金额单位
* @param refundReason 退款原因
@ -348,33 +517,58 @@ public class LakalaPayServiceImpl implements LakalaPayService {
@Override
public Pair<Boolean, String> innerLklRefund(Integer storeId, String outTradeNo, String originTradeNo, String refundAmount, String refundReason, String lklMerchantNo, String lklTermNo) {
try {
log.info("开始执行拉卡拉内部退款,参数: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, refundReason={}",
log.info("[拉卡拉退款] 开始执行拉卡拉内部退款,参数: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, refundReason={}",
storeId, outTradeNo, originTradeNo, refundAmount, refundReason);
// 1. 获取请求IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
log.error("无法获取HttpServletRequest退款失败");
log.error("[拉卡拉退款] 无法获取HttpServletRequest退款失败");
return Pair.of(false, I18nUtil._("系统异常,无法获取请求信息!"));
}
HttpServletRequest request = attributes.getRequest();
String requestIp = IpKit.getRealIp(request);
// 2. 校验参数
if (ObjectUtil.isEmpty(storeId) || org.apache.commons.lang3.StringUtils.isAnyBlank(outTradeNo, refundAmount, originTradeNo)) {
log.warn("退款请求参数不完整: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, requestIp={}", storeId, outTradeNo, originTradeNo, refundAmount, requestIp);
return Pair.of(false, I18nUtil._("缺少必要参数,退款失败!"));
if (ObjectUtil.isEmpty(storeId)) {
log.warn("[拉卡拉退款] 店铺ID不能为空: storeId={}", storeId);
return Pair.of(false, I18nUtil._("店铺ID不能为空,退款失败!"));
}
if (StrUtil.isBlank(outTradeNo)) {
log.warn("[拉卡拉退款] 外部交易订单号不能为空: outTradeNo={}", outTradeNo);
return Pair.of(false, I18nUtil._("外部交易订单号不能为空,退款失败!"));
}
if (StrUtil.isBlank(originTradeNo)) {
log.warn("[拉卡拉退款] 原拉卡拉交易流水号不能为空: originTradeNo={}", originTradeNo);
return Pair.of(false, I18nUtil._("原拉卡拉交易流水号不能为空,退款失败!"));
}
if (StrUtil.isBlank(refundAmount)) {
log.warn("[拉卡拉退款] 退款金额不能为空: refundAmount={}", refundAmount);
return Pair.of(false, I18nUtil._("退款金额不能为空,退款失败!"));
}
// 校验退款金额格式
if (!refundAmount.matches("\\d+") || Integer.parseInt(refundAmount) <= 0) {
log.warn("退款金额不合法: refundAmount={}", refundAmount);
log.warn("[拉卡拉退款] 退款金额不合法: refundAmount={}", refundAmount);
return Pair.of(false, I18nUtil._("退款金额不合法!"));
}
// 3. 初始化拉卡拉SDK
initLKLSDK();
// 4. 获取店铺的拉卡拉商户号和终端号
ShopStoreBase shopStoreBase = shopService.getLklMerchantNoAndTermNo(storeId);
if (shopStoreBase == null || org.apache.commons.lang3.StringUtils.isAnyBlank(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no())) {
log.error("无法获取店铺的拉卡拉商户号或终端号: storeId={}", storeId);
if (shopStoreBase == null) {
log.error("[拉卡拉退款] 无法获取店铺信息: storeId={}", storeId);
return Pair.of(false, I18nUtil._("无法获取店铺信息,退款失败!"));
}
if (StrUtil.isBlank(shopStoreBase.getLkl_merchant_no()) || StrUtil.isBlank(shopStoreBase.getLkl_term_no())) {
log.error("[拉卡拉退款] 无法获取店铺的拉卡拉商户号或终端号: storeId={}, merchantNo={}, termNo={}",
storeId, shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no());
return Pair.of(false, I18nUtil._("缺少商户号参数,退款失败!"));
}
@ -393,37 +587,44 @@ public class LakalaPayServiceImpl implements LakalaPayService {
refundRequest.setLocationInfo(new V3LabsTradeLocationInfo(requestIp, null, ""));
log.info("拉卡拉退款请求参数: {}", JSONUtil.toJsonStr(refundRequest));
log.info("[拉卡拉退款] 请求参数: {}", JSONUtil.toJsonStr(refundRequest));
String responseString = LKLSDK.httpPost(refundRequest);
// 6. 处理响应
if (StrUtil.isBlank(responseString)) {
log.error("拉卡拉退款接口无响应");
log.error("[拉卡拉退款] 拉卡拉退款接口无响应");
return Pair.of(false, I18nUtil._("服务端无返回值,退款失败!"));
}
log.info("拉卡拉退款接口响应: {}", responseString);
log.info("[拉卡拉退款] 拉卡拉退款接口响应: {}", responseString);
JSONObject lakalaResponseJson = JSONUtil.parseObj(responseString);
if (lakalaResponseJson == null) {
log.error("拉卡拉退款接口返回值解析失败: responseString={}", responseString);
log.error("[拉卡拉退款] 拉卡拉退款接口返回值解析失败: responseString={}", responseString);
return Pair.of(false, I18nUtil._("返回值解析失败,退款失败!"));
}
if (!"BBS00000".equals(lakalaResponseJson.getStr("code"))) {
String responseCode = lakalaResponseJson.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.error("[拉卡拉退款] 拉卡拉退款响应码为空: response={}", responseString);
return Pair.of(false, I18nUtil._("返回值格式错误,退款失败!"));
}
if (!"BBS00000".equals(responseCode)) {
String errorMessage = lakalaResponseJson.getStr("msg", "未知错误");
log.error("拉卡拉退款失败, 错误信息: {}", errorMessage);
log.error("[拉卡拉退款] 拉卡拉退款失败, 错误信息: {}, 响应码: {}", errorMessage, responseCode);
return Pair.of(false, I18nUtil._(errorMessage));
}
JSONObject responseData = lakalaResponseJson.getJSONObject("resp_data");
log.info("拉卡拉退款成功: outTradeNo={}", outTradeNo);
log.info("[拉卡拉退款] 拉卡拉退款成功: outTradeNo={}", outTradeNo);
return Pair.of(true, responseData == null ? "" : responseData.toString());
} catch (SDKException e) {
log.error("拉卡拉退款SDK异常: ", e);
log.error("[拉卡拉退款] 拉卡拉退款SDK异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款SDK异常退款失败") + e.getMessage());
} catch (Exception e) {
log.error("拉卡拉退款发生未知异常: ", e);
log.error("[拉卡拉退款] 拉卡拉退款发生未知异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款发生未知异常,退款失败!") + e.getMessage());
}
}
@ -468,331 +669,4 @@ public class LakalaPayServiceImpl implements LakalaPayService {
throw new ApiException(I18nUtil._("文件上传失败!"), e);
}
}
// @Override
// public CommonResult applyLedgerMer(JSONObject paramsJSON) {
// // 1. 配置初始化
// initLKLSDK();
//
// //2. 装配数据
// V2MmsOpenApiLedgerApplyLedgerMerRequest req = new V2MmsOpenApiLedgerApplyLedgerMerRequest();
// req.setVersion("2.0");
// String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数
// req.setOrderNo(orderNo);
// req.setOrgCode(orgCode);
// req.setMerInnerNo(paramsJSON.getStr("merInnerNo"));
// req.setMerCupNo(paramsJSON.getStr("merCupNo"));
// req.setContactMobile(paramsJSON.getStr("contactMobile"));
// req.setSplitLowestRatio(new BigDecimal(paramsJSON.getStr("splitLowestRatio")));
// String fileName = paramsJSON.getStr("splitEntrustFileName");
// req.setSplitEntrustFileName(fileName);
//
// // 分账结算委托书文件上传到拉卡拉服务器
// JSONObject fileUploadResp = uploadFile(orderNo, "SPLIT_ENTRUST_FILE", StringUtils.getFileExt(fileName), paramsJSON.getStr("splitEntrustFile"));
// if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) {
// throw new ApiException(I18nUtil._("分账结算委托书上传失败!"));
// }
//
// String splitEntrustFilePath = fileUploadResp.getStr("attFileId");
// req.setSplitEntrustFilePath(splitEntrustFilePath); //比如G1/M00/06/64/CrFdEmBQc-aAGc_XAAAiIbS3WIE960.pdf;
//
// if (isProdProject()) {
// projectDomain = projectDomain + "/api";
// }
// // 给拉卡拉通知的回调地址
// String retUrl = projectDomain + "/mobile/pay/lakala/ledger/applyLedgerMerNotify";
// req.setRetUrl(retUrl);
//
// paramsJSON.set("orderNo", orderNo);
// paramsJSON.set("version", "2.0");
// paramsJSON.set("ret_url", retUrl);
// paramsJSON.set("org_code", orgCode);
// paramsJSON.set("split_entrust_file_path", splitEntrustFilePath);
//
// try {
// //3. 发送请求
// String responseStr = LKLSDK.httpPost(req);
//
// // {'retCode':'000000','retMsg':'申请已受理请等待审核结果','respData':{'version':'1.0','orderNo':'KFPT20230223181025407788734','orgCode':'1','applyId':681201215598657536}}
// JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
// if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals("000000")) {
// throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg")));
// }
//
// paramsJSON.set("apply_id", lakalaRespJSON.getByPath("respData.applyId"));
// paramsJSON.set("remark", lakalaRespJSON.getStr("retMsg"));
// paramsJSON.set("audit_status_text", paramsJSON.get("remark"));
//
// // 新增数据
// // JSON 对象的键名转换为下划线命名
// LklLedgerMember lklLedgerMember = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMember.class);
// lklLedgerMemberService.saveOrUpdateByMerCupNo(lklLedgerMember);
//
// return CommonResult.success(null, "提交成功,待审核中!");
// } catch (SDKException e) {
// log.error("分账申请失败:", e);
// throw new ApiException(I18nUtil._("分账申请失败!"), e);
// }
// }
//
// /**
// * 商户分账业务开通申请回调
// * 参考https://o.lakala.com/#/home/document/detail?id=379
// *
// * @param request
// * @return
// */
// @Override
// public JSONObject applyLedgerMerNotify(HttpServletRequest request) {
// // 验签
// String authorization = request.getHeader("Authorization");
// String requestBody = LakalaUtil.getBody(request);
//
// boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath);
// if (!checkSuccess) {
// return JSONUtil.createObj().set("retCode", "OP90002").set("retMsg", "验签失败!");
// }
//
// JSONObject paramsJSON = JSONUtil.parseObj(requestBody);
//
// JSONObject respData = new JSONObject();
// respData.put("retCode", "OP90003");
// respData.put("retMsg", "响应处理失败!");
//
// if (paramsJSON != null && paramsJSON.get("respData") != null) {
// JSONObject reqData = (JSONObject) paramsJSON.get("respData");
//
// Boolean success = lklLedgerMemberService.updateAuditResult(reqData.getStr("applyId"),
// reqData.getStr("merInnerNo"),
// reqData.getStr("merCupNo"),
// reqData.getStr("entrustFileName"),
// reqData.getStr("entrustFilePath"),
// reqData.getStr("auditStatus"),
// reqData.getStr("auditStatusText"),
// reqData.getStr("remark"));
//
// if (success) {
// respData.put("retCode", "000000");
// respData.put("retMsg", "操作成功!");
// }
// }
//
// return respData;
// }
//
// /**
// * 分账接收方创建申请
// * 参考https://o.lakala.com/#/home/document/detail?id=380
// *
// * @param paramsJSON
// * @return
// */
// @Override
// public CommonResult applyLedgerReceiver(JSONObject paramsJSON) {
// // 1. 配置初始化
// initLKLSDK();
//
// //2. 装配数据
// V2MmsOpenApiLedgerApplyLedgerReceiverRequest req = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest();
//
// String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数
// req.setOrderNo(orderNo);
// req.setOrgCode(orgCode);
// req.setVersion("2.0");
//
// req.setReceiverName(paramsJSON.getStr("receiverName"));
// req.setContactMobile(paramsJSON.getStr("contactMobile"));
//
// req.setLicenseNo(paramsJSON.getStr("licenseNo"));
// req.setLicenseName(paramsJSON.getStr("licenseName"));
// req.setLegalPersonName(paramsJSON.getStr("legalPersonName"));
// req.setLegalPersonCertificateType(paramsJSON.getStr("legalPersonCertificateType"));
// req.setLegalPersonCertificateNo(paramsJSON.getStr("legalPersonCertificateNo"));
//
// req.setAcctNo(paramsJSON.getStr("acctNo"));
// req.setAcctName(paramsJSON.getStr("acctName"));
// req.setAcctTypeCode(paramsJSON.getStr("acctTypeCode"));
// req.setAcctCertificateType(paramsJSON.getStr("acctCertificateType"));
//
// req.setAcctCertificateNo(paramsJSON.getStr("acctCertificateNo"));
// req.setAcctOpenBankCode(paramsJSON.getStr("acctOpenBankCode"));
// req.setAcctOpenBankName(paramsJSON.getStr("acctOpenBankName"));
// req.setAcctClearBankCode(paramsJSON.getStr("acctClearBankCode"));
//
// if (paramsJSON.getJSONArray("attachList") != null && paramsJSON.getJSONArray("attachList").size() > 0) {
// List<V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo> attachList = new ArrayList<>();
// V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo attachInfo = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo();
// for (JSONObject attachJSON : paramsJSON.getJSONArray("attachList").jsonIter()) {
// String fileName = attachJSON.getStr("attachName");
// String attachType = attachJSON.getStr("attachType");
// String fileBase64 = attachJSON.getStr("attachStoreFile");
// attachInfo.setAttachName(fileName);
// attachInfo.setAttachType(attachType);
//
// JSONObject fileUploadResp = uploadFile(StringUtils.genLklOrderNo(8), attachType,
// StringUtils.getFileExt(fileName), fileBase64);
// if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) {
// throw new ApiException(I18nUtil._("附件上传失败!"));
// }
//
// attachInfo.setAttachStorePath(fileUploadResp.getStr("attFileId"));
// attachList.add(attachInfo);
// }
//
// req.setAttachList(attachList);
// paramsJSON.set("attach_list", JSONUtil.toJsonStr(attachList));
// }
//
// paramsJSON.set("orderNo", orderNo);
// paramsJSON.set("version", "2.0");
// paramsJSON.set("org_code", orgCode);
//
// try {
// //3. 发送请求申请创建分账接收方
// String responseStr = LKLSDK.httpPost(req);
//
// JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
// if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals("000000")) {
// throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg")));
// }
//
// paramsJSON.set("receiver_no", lakalaRespJSON.getByPath("respData.receiverNo"));
// paramsJSON.set("org_id", lakalaRespJSON.getByPath("respData.orgId"));
// paramsJSON.set("org_name", lakalaRespJSON.getByPath("respData.orgName"));
//
// // 新增数据
// // JSON 对象的键名转换为下划线命名
// LklLedgerReceiver lklLedgerReceiver = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerReceiver.class);
//
// // 新增或修改本地数据
// lklLedgerReceiverService.saveOrUpdateByReceiverNo(lklLedgerReceiver);
//
// return CommonResult.success(null, "接收方创建成功!");
// } catch (SDKException e) {
// log.error("接收方创建失败:", e);
// throw new ApiException(I18nUtil._("接收方创建失败!"), e);
// }
// }
//
// /**
// * 分账关系绑定申请
// * 参考https://o.lakala.com/#/home/document/detail?id=386
// *
// * @param paramsJSON
// * @return
// */
// @Override
// public CommonResult applyLedgerMerReceiverBind(JSONObject paramsJSON) {
// // 1. 配置初始化
// initLKLSDK();
//
// //2. 装配数据
// V2MmsOpenApiLedgerApplyBindRequest req = new V2MmsOpenApiLedgerApplyBindRequest();
//
// String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数
// req.setOrderNo(orderNo);
// req.setOrgCode(orgCode);
// req.setVersion("2.0");
//
// req.setMerInnerNo(paramsJSON.getStr("merInnerNo"));
// req.setMerCupNo(paramsJSON.getStr("merCupNo"));
// req.setReceiverNo(paramsJSON.getStr("receiverNo"));
//
// String fileName = paramsJSON.getStr("entrustFileName");
// String splitEntrustFileBase64 = paramsJSON.getStr("entrustFile");
// req.setEntrustFileName(fileName);
//
// String retUrl = projectDomain + "/mobile/pay/lakala/ledger/applyLedgerMerReceiverBindNotify";
// req.setRetUrl(retUrl);
//
// // 文件上传到拉卡拉服务器
// JSONObject fileUploadResp = uploadFile(orderNo,
// "SPLIT_COOPERATION_FILE",
// StringUtils.getFileExt(fileName),
// splitEntrustFileBase64);
// if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) {
// throw new ApiException(I18nUtil._("合作协议上传失败!"));
// }
//
// String entrustFilePath = fileUploadResp.getStr("attFileId");
// req.setEntrustFilePath(entrustFilePath);
//
// paramsJSON.set("orderNo", orderNo);
// paramsJSON.set("version", "2.0");
// paramsJSON.set("ret_url", retUrl);
// paramsJSON.set("org_code", orgCode);
// paramsJSON.set("entrust_file_name", fileName);
// paramsJSON.set("entrust_file_path", entrustFilePath);
//
// try {
// //3. 发送请求
// String responseStr = LKLSDK.httpPost(req);
//
// JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
// if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals("000000")) {
// throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg")));
// }
//
// paramsJSON.set("apply_id", lakalaRespJSON.getByPath("respData.applyId"));
// paramsJSON.set("remark", lakalaRespJSON.getStr("retMsg"));
//
// // 新增数据
// // JSON 对象的键名转换为下划线命名
// LklLedgerMerReceiverBind lklLedgerMerReceiverBind = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMerReceiverBind.class);
// lklLedgerMerReceiverBindService.saveOrUpdateByMerCupNoReceiverNo(lklLedgerMerReceiverBind);
//
// return CommonResult.success(null, "提交成功,待审核中!");
// } catch (SDKException e) {
// log.error("分账绑定关系申请失败:", e);
// throw new ApiException(I18nUtil._("分账绑定关系申请失败!"), e);
// }
// }
//
// /**
// * 分账关系绑定申请回调
// * 参考https://o.lakala.com/#/home/document/detail?id=379
// *
// * @param request
// * @return
// */
// @Override
// public JSONObject applyLedgerMerReceiverBindNotify(HttpServletRequest request) {
// // 验签
// String authorization = request.getHeader("Authorization");
// String requestBody = LakalaUtil.getBody(request);
//
// boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath);
// if (!checkSuccess) {
// return JSONUtil.createObj().set("retCode", "OP90002").set("retMsg", "验签失败!");
// }
//
//// String requestBody = LakalaUtil.getBody(request);
//
// JSONObject paramsJSON = JSONUtil.parseObj(requestBody);
// JSONObject respData = new JSONObject();
// respData.put("retCode", "OP90003");
// respData.put("retMsg", "响应处理失败!");
//
// if (paramsJSON != null && paramsJSON.get("respData") != null) {
// JSONObject reqData = (JSONObject) paramsJSON.get("respData");
//
// Boolean success = lklLedgerMerReceiverBindService.updateAuditResult(reqData.getStr("applyId"),
// reqData.getStr("merInnerNo"),
// reqData.getStr("merCupNo"),
// reqData.getStr("receiverNo"),
// reqData.getStr("entrustFileName"),
// reqData.getStr("entrustFilePath"),
// reqData.getStr("auditStatus"),
// reqData.getStr("auditStatusText"),
// reqData.getStr("remark"));
// if (success) {
// respData.put("retCode", "000000");
// respData.put("retMsg", "操作成功!");
// }
// }
//
// return respData;
// }
}

File diff suppressed because one or more lines are too long

View File

@ -52,6 +52,7 @@ import com.suisung.mall.common.modules.pay.*;
import com.suisung.mall.common.modules.pay.dto.ItemActivityInfoDTO;
import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.LogUtil;
@ -675,9 +676,17 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
cn.hutool.json.JSONObject lakalaRespJSON = new cn.hutool.json.JSONObject();
BigDecimal shippingFee = shopService.getOrderShippingFee(out_trade_no);
logger.debug("预支付时,查到的订单{},运费:{}", out_trade_no, shippingFee);
if (shippingFee == null || shippingFee.intValue() <= 0) {
shippingFee = BigDecimal.ZERO;
}
if (shippingFee == null || shippingFee.compareTo(BigDecimal.ZERO) <= 0) {
// 平台最低配送费单位
Integer innerMinDeliverFee = accountBaseConfigService.getInnerMinDeliveryFee();
logger.debug("预支付时,查到的订单{},商家配送费:{}元,平台最低配送费要求:{}分", out_trade_no, shippingFee, innerMinDeliverFee);
// 平台最低配送费单位
if (innerMinDeliverFee == null || innerMinDeliverFee.intValue() <= 0) {
// 没有运费
// 拉卡拉预支付返回参数
lakalaRespJSON = lakalaPayService.lklTransPreOrder(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no(),
@ -686,11 +695,12 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
requestIP, trade_remark);
} else { // 有运费的情况
// 拉卡拉合单预支付返回参数
// TODO RMK 这里只有固定一个运费代理商代收运费以后代理商模块做好了要动态读取店铺的运费代理商
// RMK 这里只有固定一个运费代理商代收运费以后代理商模块做好了要动态读取店铺的运费代理商
lakalaRespJSON = lakalaPayService.lklTransMergePreOrder(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no(),
delivery_merchant_no, delivery_term_no, // 以后根据代理商动态分配
appId, openId, storeIdStr, out_trade_no, subject, total_amt,
Convert.toStr(shippingFee.multiply(BigDecimal.valueOf(100)).intValue()),
// Convert.toStr(shippingFee.multiply(BigDecimal.valueOf(100)).intValue()),
Convert.toStr(innerMinDeliverFee),
notifyUrl,
requestIP, trade_remark);
}
@ -1344,51 +1354,69 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
}
/**
* 处理拉卡拉支付异步通知
* 该方法用于接收并处理来自拉卡拉支付平台的异步通知包括验签解析参数更新订单状态等操作
*
* @param request HTTP请求对象包含拉卡拉支付平台发送的异步通知数据
* @return String 返回处理结果用于响应拉卡拉支付平台
*/
@Override
public String lklNotifyUrl(HttpServletRequest request) {
Map<String, String> params;
try {
logger.info("[拉卡拉支付通知] 开始处理拉卡拉支付异步通知");
// 初始化拉卡拉SDK
lakalaPayService.initLKLSDK();
log.debug("[拉卡拉支付通知] 拉卡拉SDK初始化完成");
// 读取请求体并验签
String body = LKLSDK.notificationHandle(request);
String authorization = request.getHeader("Authorization");
logger.debug("[拉卡拉支付通知] 接收到通知数据body长度={}authorization长度={}",
body != null ? body.length() : 0,
authorization != null ? authorization.length() : 0);
if (StrUtil.isBlank(body)) {
log.warn("[拉卡拉支付通知] 验签失败,请求体为空");
return lklNotifyMsg(false, "验签失败!");
}
// 敏感头信息脱敏打印
logger.info("拉卡拉支付异步通知回调 body:{} \n authorization: {}", body, authorization);
// 异步通知返回的body json数据{"out_trade_no":"202203151637334864280014","trade_no":"2022031566210203291925","log_no":"66210203291925",
// "acc_trade_no":"2022031522001483661454130929 ","trade_status":"SUCCESS","trade_state":"SUCCESS","total_amount":"1",
// "payer_amount":"1","acc_settle_amount":"1","trade_time":"20220315163808","user_id1":"app***@163.com",
// "user_id2":"2088432881453660","notify_url":"https://www.baidu.com","account_type":"ALIPAY","card_type":"99"}
// 合单返回的数据{"out_trade_no":"DD-20250830-10","trade_no":"20250830110113130266250034160499","log_no":"66250034160499","acc_trade_no":"4200002826202508306761393882","trade_status":"SUCCESS","trade_state":"SUCCESS","total_amount":"2","payer_amount":"2","acc_settle_amount":"2","acc_mdiscount_amount":"0","acc_discount_amount":"0","trade_time":"20250830180435","user_id1":"oDVKR7T0qxg6O8tqIL9SgY6LXqqQ","user_id2":"oVxsc1QRAqDRv_gAmXuLZwSVSL18","notify_url":"https://mall.gpxscs.cn/mobile/pay/index/lkl_wxPay_notify_url","account_type":"WECHAT","bank_type":"OTHERS","card_type":"02","merchant_no":"8226330541100GU","remark":"","sub_mch_id":"803819329","out_split_rsp_infos":[{"sub_trade_no":"20250830110113130266250034112794","sub_log_no":"66250034112794","out_sub_trade_no":"ORD_DD-20250830-10","merchant_no":"8226330541100GU","term_no":"N5817779","amount":"1","settle_type":"0"},{"sub_trade_no":"20250830110113130266250034160498","sub_log_no":"66250034160498","out_sub_trade_no":"DF_DD-20250830-10","merchant_no":"822584059990FYP","term_no":"N5811590","amount":"1","settle_type":"0"}],"trade_req_date":"20250830","gb_amount":"","qb_amount":""}
logger.info("[拉卡拉支付通知] 拉卡拉支付异步通知回调 body:{} \n authorization: {}",
body, authorization != null ? "***" : "null");
// 解析JSON格式响应
cn.hutool.json.JSONObject lklNotifyRespJSON = JSONUtil.parseObj(body);
logger.debug("[拉卡拉支付通知] 解析JSON完成: keys={}", lklNotifyRespJSON.keySet());
params = Convert.toMap(String.class, String.class, lklNotifyRespJSON);
String orderId = params.getOrDefault("out_trade_no", "");
String accTradeNo = params.getOrDefault("acc_trade_no", ""); // 需要跟拉卡拉确认这个字段是原支付交易对应的微信订单号吗
String outSplitRspInfos = params.getOrDefault("out_split_rsp_infos", ""); // 拉卡拉合单订单信息
String tradeStatus = params.getOrDefault("trade_status", "");
String accTradeNo = params.getOrDefault("acc_trade_no", "");
String outSplitRspInfos = params.getOrDefault("out_split_rsp_infos", "");
logger.info("[拉卡拉支付通知] 核心参数 - 订单号:{} 状态:{} 是否合单:{}",
orderId, tradeStatus, StrUtil.isNotBlank(outSplitRspInfos) ? "" : "");
// 提取授权签名信息
Map<String, String> authMap = LakalaUtil.getLakalaAuthorizationMap(authorization);
if (authMap != null && authMap.containsKey("signature")) {
params.put("sign", authMap.get("signature"));
logger.debug("[拉卡拉支付通知] 签名信息提取成功");
} else {
logger.error("缺少签名信息");
logger.error("[拉卡拉支付通知] 缺少签名信息authMap={}", authMap);
return lklNotifyMsg(false, "缺少签名信息");
}
if (StrUtil.isBlank(orderId)) {
logger.error("缺少out_trade_no字段");
logger.error("[拉卡拉支付通知] 缺少out_trade_no字段body={}", body);
return lklNotifyMsg(false, "缺少out_trade_no字段");
}
// 查询交易信息
logger.debug("[拉卡拉支付通知] 查询交易信息: orderId={}", orderId);
QueryWrapper<PayConsumeTrade> tradeQueryWrapper = new QueryWrapper<>();
tradeQueryWrapper.eq("order_id", orderId);
PayConsumeTrade trade_row_tmp = payConsumeTradeService.findOne(tradeQueryWrapper);
@ -1397,25 +1425,32 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
String orderSubject = trade_row_tmp != null ? trade_row_tmp.getTrade_title() : "";
Integer userId = trade_row_tmp != null ? trade_row_tmp.getBuyer_id() : 0;
logger.debug("[拉卡拉支付通知] 交易信息查询完成: storeId={} userId={}", payment_store_id, userId);
// 更新交易记录的 原支付交易对应的微信订单号 transaction_id
if (trade_row_tmp != null && StrUtil.isNotBlank(accTradeNo)) {
logger.debug("[拉卡拉支付通知] 更新交易记录的微信订单号: tradeId={} accTradeNo={}",
trade_row_tmp.getConsume_trade_id(), accTradeNo);
PayConsumeTrade payConsumeTradeUpd = new PayConsumeTrade();
payConsumeTradeUpd.setConsume_trade_id(trade_row_tmp.getConsume_trade_id()).setTransaction_id(accTradeNo);
payConsumeTradeService.updateTradeByPrimaryKey(payConsumeTradeUpd);
}
// 查询支付渠道
logger.debug("[拉卡拉支付通知] 查询支付渠道: channelCode=lakala");
QueryWrapper<PayPaymentChannel> channelQueryWrapper = new QueryWrapper<>();
channelQueryWrapper.eq("payment_channel_code", "lakala");
PayPaymentChannel payPaymentChannel = payPaymentChannelService.findOne(channelQueryWrapper);
if (payPaymentChannel == null) {
logger.error("支付渠道不存在");
logger.error("[拉卡拉支付通知] 支付渠道不存在: channelCode=lakala");
return lklNotifyMsg(false, "支付渠道不存在");
}
Integer payment_channel_id = payPaymentChannel.getPayment_channel_id();
logger.debug("[拉卡拉支付通知] 支付渠道查询完成: channelId={}", payment_channel_id);
// 插入充值记录
logger.debug("[拉卡拉支付通知] 创建充值记录");
PayConsumeDeposit payConsumeDeposit = createNotify(params, payPaymentChannel);
payConsumeDeposit.setOrder_id(orderId);
payConsumeDeposit.setStore_id(payment_store_id); // 所属店铺
@ -1425,47 +1460,89 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
payConsumeDeposit.setDeposit_body(orderSubject);
payConsumeDeposit.setUser_id(userId);
// 设置拉卡拉相关参数
String merchantNo = lklNotifyRespJSON.getStr("merchant_no");
lklNotifyRespJSON.set("out_separate_no", orderId);// 默认非合单主单订单号
lklNotifyRespJSON.set("lkl_sub_trade_no", lklNotifyRespJSON.getStr("trade_no"));// 默认非合单主单交易号
lklNotifyRespJSON.set("lkl_sub_log_no", lklNotifyRespJSON.getStr("log_no")); // 默认非合单主单的对账流水号
lklNotifyRespJSON.set("split_amt", lklNotifyRespJSON.getStr("total_amount")); // 默认非合单主单支付金额
logger.debug("[拉卡拉支付通知] 基础参数设置完成: out_separate_no={} lkl_sub_trade_no={} lkl_sub_log_no={} split_amt={}",
lklNotifyRespJSON.getStr("out_separate_no"),
lklNotifyRespJSON.getStr("lkl_sub_trade_no"),
lklNotifyRespJSON.getStr("lkl_sub_log_no"),
lklNotifyRespJSON.getStr("split_amt"));
// 拉卡拉订单合单信息
if (StrUtil.isNotBlank(outSplitRspInfos)) {
logger.debug("[拉卡拉支付通知] 处理合单信息");
// [{"sub_trade_no":"20250830110113130266250034401288","merchant_no":"822584059990FYP","amount":"1","settle_type":"0","sub_log_no":"66250034401288","out_sub_trade_no":"DF-DD-20250830-21","term_no":"N5811590"},{"sub_trade_no":"20250830110113130266250034401289","merchant_no":"8226330541100GU","amount":"1","settle_type":"0","sub_log_no":"66250034401289","out_sub_trade_no":"ORD-DD-20250830-21","term_no":"N5817779"}]
payConsumeDeposit.setLkl_combine_params(outSplitRspInfos);
Pair<String, String> subTradeNos = getLklSubTradeNo(outSplitRspInfos);
if (subTradeNos != null) {
// 商品子订单号
payConsumeDeposit.setOrd_sub_trade_no(subTradeNos.getFirst());
// 商品运费子订单号
payConsumeDeposit.setDf_sub_trade_no(subTradeNos.getSecond());
logger.debug("[拉卡拉支付通知] 子订单号解析完成: ord={} df={}",
subTradeNos.getFirst(), subTradeNos.getSecond());
}
// 获取拉卡拉合单商品子订单信息
cn.hutool.json.JSONObject goodsOrderInfo = CommonService.getLklCombineSplitRespInfo(merchantNo, outSplitRspInfos, false);
if (goodsOrderInfo != null) {
lklNotifyRespJSON.set("out_separate_no", goodsOrderInfo.getStr("out_sub_trade_no"));// 合单商品子订单号
lklNotifyRespJSON.set("lkl_sub_trade_no", goodsOrderInfo.getStr("sub_trade_no")); // 合单商品子订单的流水号
lklNotifyRespJSON.set("lkl_sub_log_no", goodsOrderInfo.getStr("sub_log_no")); // 合单商品子订单的流水号
lklNotifyRespJSON.set("split_amt", goodsOrderInfo.getStr("amount")); // 合单子商品订单支付金额
logger.debug("[拉卡拉支付通知] 合单信息更新完成: out_separate_no={} lkl_sub_trade_no={} lkl_sub_log_no={} split_amt={}",
lklNotifyRespJSON.getStr("out_separate_no"),
lklNotifyRespJSON.getStr("lkl_sub_trade_no"),
lklNotifyRespJSON.getStr("lkl_sub_log_no"),
lklNotifyRespJSON.getStr("split_amt"));
}
}
// 判断是否联合支付
logger.debug("[拉卡拉支付通知] 查询联合支付信息: orderId={}", orderId);
PayConsumeTradeCombine tradeCombine = payConsumeTradeCombineService.get(orderId);
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
logger.debug("[拉卡拉支付通知] 开始事务处理");
// 修改 拉卡拉的订单记录 shop_order_lkl
logger.debug("[拉卡拉支付通知] 调用shopService更新拉卡拉订单记录");
shopService.lklPayNotifyUpdateShopOrderLkl(lklNotifyRespJSON);
log.debug("[拉卡拉支付通知] 拉卡拉订单记录更新完成");
if (tradeCombine != null && StrUtil.isNotBlank(tradeCombine.getOrder_ids())) {
logger.debug("[拉卡拉支付通知] 处理联合支付: combinedOrderIds={}", tradeCombine.getOrder_ids());
payConsumeDeposit.setOrder_id(tradeCombine.getOrder_ids());
}
// 重要支付完成接口调用
log.debug("[拉卡拉支付通知] 调用支付完成处理接口");
if (!payConsumeDepositService.processDeposit(payConsumeDeposit, BigDecimal.ZERO, BigDecimal.ZERO,
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)) {
log.error("[processDeposit]处理订单数据失败!");
logger.error("[拉卡拉支付通知][processDeposit]处理订单数据失败!");
return lklNotifyMsg(false, "支付失败!");
}
log.debug("[拉卡拉支付通知] 支付完成处理接口调用成功");
transactionManager.commit(transactionStatus);
logger.info("[拉卡拉支付通知] 事务提交成功,支付处理完成: orderId={}", orderId);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("接收拉卡拉支付异步通知出错:{}", e);
logger.error("[拉卡拉支付通知] 事务处理出错,已回滚: orderId={}", orderId, e);
throw new ApiException(e.getMessage());
}
return lklNotifyMsg(true, "执行成功");
} catch (Exception e) {
logger.error("通知处理发生异常{}", e.getMessage(), e);
logger.error("[拉卡拉支付通知] 通知处理发生异常", e);
return lklNotifyMsg(false, "通知处理发生异常!");
}
}
@ -1900,13 +1977,13 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
}
// 6. 根据前缀分类处理
if (outSubTradeNo.startsWith("ORD-")) {
if (outSubTradeNo.startsWith(CommonConstant.Sep_GoodsFee_Prefix)) {
// 处理商品订单
if (productSubTradeNo != null) {
logger.warn("检测到重复的商品订单子流水号:{}", outSubTradeNo);
}
productSubTradeNo = item.getStr("sub_trade_no");
} else if (outSubTradeNo.startsWith("DF-")) {
} else if (outSubTradeNo.startsWith(CommonConstant.Sep_DeliveryFee_Prefix)) {
// 处理运费订单
if (shippingSubTradeNo != null) {
logger.warn("检测到重复的运费子流水号:{}", outSubTradeNo);

View File

@ -131,25 +131,26 @@
<artifactId>restful-sdk</artifactId>
<version>1.0.0.6</version>
</dependency>
<!-- JavaCV库 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<!-- 引入常用windows和linux平台 其他平台用到时在引入 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.4-1.5.6</version>
<classifier>windows-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.4-1.5.6</version>
<classifier>linux-x86_64</classifier>
</dependency>
<!-- &lt;!&ndash; JavaCV库 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>javacv</artifactId>-->
<!-- <version>1.5.6</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; JavaCV库 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>javacv-platform</artifactId>-->
<!-- <version>1.5.6</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; FFmpeg平台依赖 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>ffmpeg-platform</artifactId>-->
<!-- <version>4.4-1.5.6</version>-->
<!-- </dependency>-->
<!-- rabbitMQ消息队列 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -272,6 +273,11 @@
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
@ -324,6 +330,21 @@
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<!-- 开发环境使用多平台依赖 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
</dependencies>
</profile>
<!--开发环境 -->
<profile>
@ -334,6 +355,22 @@
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<!-- 开发环境使用多平台依赖 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
</dependencies>
</profile>
<!--测试环境 -->
<profile>
@ -341,6 +378,22 @@
<properties>
<profiles.active>test</profiles.active>
</properties>
<dependencies>
<!-- 开发环境使用多平台依赖 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
</dependencies>
</profile>
<!--uat环境 -->
<profile>
@ -348,6 +401,22 @@
<properties>
<profiles.active>uat</profiles.active>
</properties>
<dependencies>
<!-- 开发环境使用多平台依赖 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency>
</dependencies>
</profile>
<!--生产环境 -->
<profile>
@ -355,6 +424,24 @@
<properties>
<profiles.active>prod</profiles.active>
</properties>
<dependencies>
<!-- 生产环境仅包含Linux平台依赖 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency>
<!-- Linux平台的FFmpeg -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>4.4-1.5.6</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
</profile>
</profiles>
@ -391,10 +478,12 @@
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>

View File

@ -39,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -239,8 +240,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (yestodayOrderNum.getOrderNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (todayOrderNum.getOrderNum().subtract(yestodayOrderNum.getOrderNum())).divide(yestodayOrderNum.getOrderNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (todayOrderNum.getOrderNum().subtract(yestodayOrderNum.getOrderNum())).divide(yestodayOrderNum.getOrderNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -316,8 +317,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -370,8 +371,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -423,8 +424,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -483,8 +484,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -564,8 +565,8 @@ public class AnalyticsOrderServiceImpl implements AnalyticsOrderService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
public class AnalyticsProductServiceImpl implements AnalyticsProductService {
@ -51,8 +52,8 @@ public class AnalyticsProductServiceImpl implements AnalyticsProductService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -91,8 +92,8 @@ public class AnalyticsReturnServiceImpl implements AnalyticsReturnService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -150,8 +151,8 @@ public class AnalyticsReturnServiceImpl implements AnalyticsReturnService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -205,8 +206,8 @@ public class AnalyticsReturnServiceImpl implements AnalyticsReturnService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -287,8 +288,8 @@ public class AnalyticsReturnServiceImpl implements AnalyticsReturnService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
@ -52,8 +53,8 @@ public class AnalyticsStoreServiceImpl implements AnalyticsStoreService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (yestodayRegUser.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (todayRegUser.getNum().subtract(yestodayRegUser.getNum())).divide(yestodayRegUser.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (todayRegUser.getNum().subtract(yestodayRegUser.getNum())).divide(yestodayRegUser.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -113,8 +114,8 @@ public class AnalyticsStoreServiceImpl implements AnalyticsStoreService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preRegNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentRegNum.getNum().subtract(preRegNum.getNum())).divide(preRegNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@Service
@ -59,8 +60,8 @@ public class AnalyticsSysServiceImpl implements AnalyticsSysService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (yestodayVisits.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (todayVisits.getNum().subtract(yestodayVisits.getNum())).divide(yestodayVisits.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (todayVisits.getNum().subtract(yestodayVisits.getNum())).divide(yestodayVisits.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -105,8 +106,8 @@ public class AnalyticsSysServiceImpl implements AnalyticsSysService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -144,8 +145,8 @@ public class AnalyticsSysServiceImpl implements AnalyticsSysService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -207,8 +208,8 @@ public class AnalyticsSysServiceImpl implements AnalyticsSysService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}
@ -270,8 +271,8 @@ public class AnalyticsSysServiceImpl implements AnalyticsSysService {
// 计算日环比 日环比 = (当日数据 - 前一日数据) / 前一日数据 * 100%
BigDecimal daym2m = BigDecimal.ZERO;
if (preProductNum.getNum().compareTo(BigDecimal.ZERO) != 0) {
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, BigDecimal.ROUND_HALF_UP);
//.multiply(new BigDecimal("100"));
daym2m = (currentProductNum.getNum().subtract(preProductNum.getNum())).divide(preProductNum.getNum(), 2, RoundingMode.HALF_UP);
//.multiply(new BigDecimal(100));
} else {
}

View File

@ -95,7 +95,7 @@ public class ShopBaseStoreCategoryAdminController {
return CommonResult.failed(I18nUtil._("分成比例不能小于94"));
}
if (shopBaseStoreCategory.getSplit_ratio().compareTo(new BigDecimal("100")) > 0) {
if (shopBaseStoreCategory.getSplit_ratio().compareTo(new BigDecimal(100)) > 0) {
return CommonResult.failed(I18nUtil._("分成比例不能大于100"));
}

View File

@ -11,6 +11,8 @@ import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.base.mapper.ShopBaseCrontabMapper;
import com.suisung.mall.shop.base.service.ShopBaseCrontabService;
import com.suisung.mall.shop.components.quartz.service.QuartzService;
import com.suisung.mall.shop.components.quartz.service.impl.QuartzServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,16 +26,16 @@ import org.springframework.transaction.annotation.Transactional;
* @author Xinze
* @since 2021-08-16
*/
@Slf4j
@Service
public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabMapper, ShopBaseCrontab> implements ShopBaseCrontabService {
@Autowired
private QuartzService quartzService;
private final String JOB_NAME_PREFIX = "JOB_";
private final String TRIGGER_NAME_PREFIX = "TRIGGER_";
private final String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP";
private final String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP";
@Autowired
private QuartzService quartzService;
@Transactional
@Override
@ -48,7 +50,7 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(ResultCode.FAILED);
}
if (CheckUtil.isNotEmpty(crontab_enable)) {
if (CheckUtil.isNotEmpty(crontab_enable) && crontab_enable == 1) {
addJob(shopBaseCrontab);
}
} else {
@ -58,9 +60,26 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(ResultCode.FAILED);
}
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
if (crontab_enable == 1) {
addJob(shopBaseCrontab);
try {
// 先尝试删除已存在的任务如果存在
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
} catch (Exception e) {
// 删除失败记录日志但不中断操作
log.warn("删除旧任务时发生异常: 任务ID={}", crontab_id, e);
}
// 如果任务启用则添加新任务
if (crontab_enable != null && crontab_enable == 1) {
try {
addJob(shopBaseCrontab);
} catch (Exception e) {
log.error("添加新任务时发生异常: 任务ID={}", crontab_id, e);
// 如果添加失败尝试从数据库同步任务
boolean syncResult = syncSingleCrontabToScheduler(crontab_id);
if (!syncResult) {
throw new ApiException("添加定时任务失败,且同步任务也失败", e);
}
}
}
}
@ -72,7 +91,13 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
public boolean removeCrontab(String crontab_id) {
boolean flag = remove(crontab_id);
if (flag) {
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
try {
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
} catch (Exception e) {
// 删除失败时尝试从数据库同步确保一致性
log.warn("删除任务时发生异常: 任务ID={}", crontab_id, e);
// 即使删除失败也继续执行避免阻塞业务流程
}
}
return flag;
}
@ -84,10 +109,19 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
*/
@Override
public void addJob(ShopBaseCrontab shopBaseCrontab) {
String crontab_file = shopBaseCrontab.getCrontab_file();
Integer crontab_id = shopBaseCrontab.getCrontab_id();
String cron = getCron(shopBaseCrontab);
quartzService.addJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME, TRIGGER_NAME_PREFIX + crontab_id, TRIGGER_GROUP_NAME, cron, crontab_file);
try {
String crontab_file = shopBaseCrontab.getCrontab_file();
Integer crontab_id = shopBaseCrontab.getCrontab_id();
String cron = getCron(shopBaseCrontab);
quartzService.addJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME, TRIGGER_NAME_PREFIX + crontab_id, TRIGGER_GROUP_NAME, cron, crontab_file);
} catch (Exception e) {
log.error("添加定时任务失败,尝试从数据库同步: 任务ID={}", shopBaseCrontab.getCrontab_id(), e);
// 如果添加失败尝试从数据库同步任务
boolean syncResult = syncSingleCrontabToScheduler(shopBaseCrontab.getCrontab_id());
if (!syncResult) {
throw new ApiException("添加定时任务失败,且同步任务也失败", e);
}
}
}
/**
@ -111,12 +145,60 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(I18nUtil._("每周和每月不能同时选择!"));
}
StringBuilder cron = new StringBuilder();
String space = " ";
cron.append("0").append(space).append(crontab_minute).append(space).append(crontab_hour).append(space).append(crontab_day).append(space).append(crontab_month).append(space).append(crontab_week);
return "0" + space + crontab_minute + space + crontab_hour + space + crontab_day + space + crontab_month + space + crontab_week;
}
return cron.toString();
/**
* 从数据库同步单个任务到调度器
*
* @param crontabId 任务ID
* @return 是否同步成功
*/
public boolean syncSingleCrontabToScheduler(Integer crontabId) {
try {
// 从数据库获取任务信息
ShopBaseCrontab crontab = getById(crontabId);
if (crontab == null) {
log.warn("未找到ID为{}的定时任务", crontabId);
return false;
}
// 检查任务是否启用
if (crontab.getCrontab_enable() == null || crontab.getCrontab_enable() != 1) {
log.info("任务ID为{}的定时任务未启用,无需同步到调度器", crontabId);
return true;
}
// 构造任务和触发器名称
String jobName = JOB_NAME_PREFIX + crontabId;
String jobGroup = JOB_GROUP_NAME;
String triggerName = TRIGGER_NAME_PREFIX + crontabId;
String triggerGroup = TRIGGER_GROUP_NAME;
// 生成cron表达式
String cron = getCron(crontab);
// 获取任务类名
String jobClass = crontab.getCrontab_file();
if (jobClass != null && jobClass.contains(".")) {
jobClass = jobClass.substring(0, jobClass.lastIndexOf("."));
}
// 通过QuartzService添加任务
if (quartzService instanceof QuartzServiceImpl) {
return ((QuartzServiceImpl) quartzService).addOrUpdateJobFromDatabase(
jobName, jobGroup, triggerName, triggerGroup, cron, jobClass);
} else {
// 兼容其他实现
quartzService.addJob(jobName, jobGroup, triggerName, triggerGroup, cron, jobClass);
return true;
}
} catch (Exception e) {
log.error("同步单个定时任务到调度器失败: 任务ID={}", crontabId, e);
return false;
}
}
}

View File

@ -32,7 +32,7 @@ public class ShopBaseStateCodeServiceImpl extends BaseServiceImpl<ShopBaseStateC
* 根据商品状态码 获取状态名称 (注意在该类内部调用此方法缓存则失效 并且 此表数据如果发生变动则需要靠重启JVM来刷新缓存)
* todo 可以优化这里缓存的是一个字段不是一条数据
* todo 一般情况此表不会发生变化如果追求数据一致性可以考虑使用redis Hash来存储使用JVM来缓存是更好的选择
*
* <p>
* update 2024-12-14 切换到redis缓存 30秒
*
* @param code 商品状态码
@ -72,12 +72,12 @@ public class ShopBaseStateCodeServiceImpl extends BaseServiceImpl<ShopBaseStateC
/**
* @param code
* @param type 默认值 "state_code_code"
* todo 加缓存
* todo 加缓存
*/
@Override
public String getCode(Integer code, String type) {
if (code != null) {
// String code_key = String.format("statecode-%s", code);
// String code_key = String.format("statecode_%s", code);
Map rows = Convert.toMap(String.class, Object.class, get(code));
if (rows != null) {

View File

@ -5,13 +5,14 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.modules.base.ShopBaseStoreCategory;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.CommonUtil;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.base.mapper.ShopBaseStoreCategoryMapper;
import com.suisung.mall.shop.base.service.ShopBaseStoreCategoryService;
import lombok.extern.slf4j.Slf4j;
import com.suisung.mall.shop.store.service.ShopStoreBaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@ -66,7 +67,7 @@ public class ShopBaseStoreCategoryServiceImpl extends BaseServiceImpl<ShopBaseSt
cache_key = cache_key + ":category_is_enable:" + category_is_enable;
queryWrapper.eq("category_is_enable", category_is_enable);
}
Integer pageSize =ObjectUtil.defaultIfNull(getParameter("pageSize",Integer.class),10) ;
Integer pageSize = ObjectUtil.defaultIfNull(getParameter("pageSize", Integer.class), 10);
cache_key = cache_key + ":" + LANG;
// 设置cache todo 关闭缓存修改分类的时候没删除缓存
@ -79,39 +80,39 @@ public class ShopBaseStoreCategoryServiceImpl extends BaseServiceImpl<ShopBaseSt
category_rows = Convert.toList(Map.class, categories);
List<CompletableFuture<Void>> futures = new ArrayList<>();
Map<String,Map> storeLis = new HashMap<>();
if(openFindStore){
Map<String, Map> storeLis = new HashMap<>();
if (openFindStore) {
long startTime = System.currentTimeMillis();
for (Map category_row : category_rows) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes,true);
RequestContextHolder.setRequestAttributes(requestAttributes, true);
Integer store_category_id = (Integer) category_row.get("store_category_id");
Map<String, Object> row = new HashMap<>();
row.put("store_category_id", String.valueOf(store_category_id));
row.put("findStore",true);
row.put("store_type","1");
Map storeListMap= shopStoreBaseService.getStoreList(1, pageSize,row);
row.put("findStore", true);
row.put("store_type", "1");
Map storeListMap = shopStoreBaseService.getStoreList(1, pageSize, row);
storeLis.put(String.valueOf(store_category_id), storeListMap);
return null;
});
futures.add(future);
}
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long endTime = System.currentTimeMillis();
SwingUtilities.invokeLater(() -> {
log.debug("异步调用完成! 总耗时: "+(endTime - startTime) + "ms\n");
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long endTime = System.currentTimeMillis();
SwingUtilities.invokeLater(() -> {
log.debug("异步调用完成! 总耗时: " + (endTime - startTime) + "ms\n");
});
});
});
allOfFuture.join();
}
for (Map category_row : category_rows) {
Integer store_category_id = (Integer) category_row.get("store_category_id");
List<Map> rs = getCategoryTree(store_category_id, category_is_enable);
if(openFindStore){
if (openFindStore) {
category_row.put("storeList", storeLis.get(String.valueOf(store_category_id)));
}
category_row.put("id", category_row.get("store_category_id"));
@ -122,7 +123,7 @@ public class ShopBaseStoreCategoryServiceImpl extends BaseServiceImpl<ShopBaseSt
}
if (CollUtil.isNotEmpty(category_rows)) {
// redisService.set(cache_key, category_rows);
// redisService.set(cache_key, category_rows);
}
}
@ -167,17 +168,17 @@ public class ShopBaseStoreCategoryServiceImpl extends BaseServiceImpl<ShopBaseSt
@Override
public BigDecimal getStoreCategoryRatio(Integer storeCategoryId) {
// 默认分账比例为100%
BigDecimal defaultRatio = new BigDecimal("100");
BigDecimal defaultRatio = new BigDecimal(94);
try {
// 检查参数是否为空
if (ObjectUtil.isEmpty(storeCategoryId)) {
if (CheckUtil.isEmpty(storeCategoryId)) {
return defaultRatio;
}
// 根据ID获取店铺分类信息
ShopBaseStoreCategory shopBaseStoreCategory = get(storeCategoryId);
if (ObjectUtil.isEmpty(shopBaseStoreCategory)) {
if (shopBaseStoreCategory == null) {
return defaultRatio;
}

View File

@ -30,6 +30,12 @@ public class IpUtil implements ApplicationRunner {
* ip2region搜索器实例
*/
private static Searcher searcher = null;
/**
* 数据文件加载状态
*/
private static boolean dataFileLoaded = false;
private static String dataFileInfo = "未加载";
/**
* 根据IP地址获取地区信息
@ -39,18 +45,23 @@ public class IpUtil implements ApplicationRunner {
*/
public static String getRegion(String ip) {
if (Objects.isNull(searcher)) {
log.error("IP2RegionUtils 没有成功加载数据文件");
log.error("IP2RegionUtils 没有成功加载数据文件. 数据文件状态: {}, 文件信息: {}",
dataFileLoaded ? "已加载但searcher为null" : "未加载", dataFileInfo);
return null;
}
if (CheckUtil.isEmpty(ip)) {
log.debug("传入的IP地址为空");
return null;
}
try {
return searcher.search(ip);
String result = searcher.search(ip);
log.debug("IP地址 {} 解析成功,结果: {}", ip, result);
return result;
} catch (ArrayIndexOutOfBoundsException e) {
log.error("IP{} 解析时发生数组越界异常,可能是数据文件损坏或不兼容", ip, e);
log.error("IP{} 解析时发生数组越界异常,可能是数据文件损坏或不兼容. 数据文件状态: {}, 文件信息: {}",
ip, dataFileLoaded ? "已加载" : "未加载", dataFileInfo, e);
return null;
} catch (Exception e) {
String errorMsg = e.getMessage();
@ -60,7 +71,13 @@ public class IpUtil implements ApplicationRunner {
errorMsg += " -> " + e.getCause().getMessage();
}
}
log.error("IP{} 格式错误:{}", ip, errorMsg, e);
log.error("IP{} 格式错误:{}. 数据文件状态: {}, 文件信息: {}",
ip, errorMsg, dataFileLoaded ? "已加载" : "未加载", dataFileInfo, e);
return null;
} catch (Throwable t) {
// 捕获所有可能的异常确保不会传播到调用方
log.error("IP{} 解析时发生未知错误. 数据文件状态: {}, 文件信息: {}",
ip, dataFileLoaded ? "已加载" : "未加载", dataFileInfo, t);
return null;
}
}
@ -97,17 +114,24 @@ public class IpUtil implements ApplicationRunner {
districtVo.setCountry(parts[0] != null ? parts[0] : ""); // 设置国家
districtVo.setProvince(parts[2] != null ? parts[2] : ""); // 设置省份
districtVo.setCity(parts[3] != null ? parts[3] : ""); // 设置城市
log.debug("IP地址 {} 解析为地区信息成功. 国家: {}, 省份: {}, 城市: {}",
ip, districtVo.getCountry(), districtVo.getProvince(), districtVo.getCity());
} else {
log.debug("IP{} 解析结果格式不符合要求,原始数据: {}", ip, ss);
return null;
}
} catch (ArrayIndexOutOfBoundsException e) {
log.error("解析IP地址 {} 的地区信息时发生数组越界异常,原始数据: {}", ip, ss, e);
log.error("解析IP地址 {} 的地区信息时发生数组越界异常,原始数据: {}. 数据文件状态: {}, 文件信息: {}",
ip, ss, dataFileLoaded ? "已加载" : "未加载", dataFileInfo, e);
return null;
} catch (Exception e) {
log.error("解析IP地址 {} 的地区信息时发生异常: {}", ip, e.getMessage(), e);
log.error("解析IP地址 {} 的地区信息时发生异常: {}. 数据文件状态: {}, 文件信息: {}",
ip, e.getMessage(), dataFileLoaded ? "已加载" : "未加载", dataFileInfo, e);
return null;
}
} else {
log.debug("IP地址 {} 未解析到地区信息", ip);
}
return districtVo;
@ -137,16 +161,29 @@ public class IpUtil implements ApplicationRunner {
buffer.flush();
byte[] bytes = buffer.toByteArray();
// 记录数据文件信息
dataFileInfo = String.format("文件大小: %d 字节", bytes.length);
log.info("读取到 ip2region 数据文件,{}", dataFileInfo);
inputStream.close();
searcher = Searcher.newWithBuffer(bytes);
log.info("成功加载 ip2region 数据文件。");
dataFileLoaded = true;
// 测试搜索器
if (searcher != null) {
String testResult = searcher.search("8.8.8.8");
log.info("成功加载 ip2region 数据文件。测试搜索 8.8.8.8 结果: {}", testResult);
} else {
log.error("加载 ip2region 数据文件失败searcher 实例为 null");
}
} catch (ArrayIndexOutOfBoundsException e) {
log.error("解析IP地址的地区信息时发生数组越界异常原始数据: {}", e);
dataFileLoaded = true;
log.error("解析IP地址的地区信息时发生数组越界异常。数据文件状态: 已加载, 文件信息: {}", dataFileInfo, e);
} catch (IOException e) {
log.error("加载 ip2region 失败。", e);
log.error("加载 ip2region 失败。数据文件状态: 未加载, 文件信息: {}", dataFileInfo, e);
} catch (Exception e) {
log.error("初始化 ip2region 搜索器失败。", e);
log.error("初始化 ip2region 搜索器失败。数据文件状态: 未加载, 文件信息: {}", dataFileInfo, e);
}
}
}

View File

@ -1,15 +1,11 @@
package com.suisung.mall.shop.components.quartz.job;
import com.suisung.mall.common.utils.LogUtil;
import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.order.service.ShopOrderReturnService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.List;
/**
* 支付宝/微信退款 定时任务
*/
@ -22,20 +18,19 @@ public class UpdateOrderRefundJob extends QuartzJobBean {
String[] activeProfiles = environment.getActiveProfiles();
String activeProfile = activeProfiles[0];
if (!activeProfile.equals("prod")) {
return;
}
ShopOrderReturnService orderReturnService = SpringUtil.getBean(ShopOrderReturnService.class);
// 获取支付宝/微信退款订单
List<String> return_ids = orderReturnService.getOnlineRefundReturnId();
for (String return_id : return_ids) {
if (!orderReturnService.doOnlineRefund(return_id)) {
LogUtil.error(String.format("return_id: %s 原路退回出错", return_id));
// 处理异常标记
}
}
// ShopOrderReturnService orderReturnService = SpringUtil.getBean(ShopOrderReturnService.class);
//
// // 获取支付宝/微信退款订单
// List<String> return_ids = orderReturnService.getOnlineRefundReturnId();
// for (String return_id : return_ids) {
// if (!orderReturnService.doOnlineRefund(return_id)) {
// LogUtil.error(String.format("return_id: %s 原路退回出错", return_id));
//
// // 处理异常标记
// }
// }
}
}

View File

@ -0,0 +1,40 @@
package com.suisung.mall.shop.components.quartz.job;
import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.lakala.service.LakalaApiService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* 分账状态更改补偿 定时任务
*/
public class UpdateOrderSeparateJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(UpdateOrderSeparateJob.class);
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("[分账状态修复定时任务] 开始执行");
try {
LakalaApiService lakalaApiService = SpringUtil.getBean(LakalaApiService.class);
if (lakalaApiService == null) {
logger.error("[分账状态修复定时任务] 无法获取LakalaApiService实例");
return;
}
Integer processedCount = lakalaApiService.fixedUnSuccessSeparateStatusJob();
logger.info("[分账状态修复定时任务] 执行完成,共处理 {} 条记录", processedCount);
} catch (Exception e) {
logger.error("[分账状态修复定时任务] 执行过程中发生异常", e);
throw new JobExecutionException(e);
}
}
}

View File

@ -4,17 +4,19 @@ import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.shop.components.quartz.service.QuartzService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class QuartzServiceImpl implements QuartzService {
private static final Logger logger = LoggerFactory.getLogger(QuartzServiceImpl.class);
private final String PATH_PREFIX = "com.suisung.mall.shop.components.quartz.job.";
@Autowired
private Scheduler scheduler;
private final String PATH_PREFIX = "com.suisung.mall.shop.components.quartz.job.";
/**
* 新增一个定时任务
*
@ -27,24 +29,126 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void addJob(String jName, String jGroup, String tName, String tGroup, String cron, String cName) {
logger.info("[Quartz] 开始添加定时任务: 任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}, cron表达式={}, 类名={}",
jName, jGroup, tName, tGroup, cron, cName);
Class<Job> clazz = null;
// 1. 参数校验
if (jName == null || jName.trim().isEmpty()) {
logger.warn("[Quartz] 任务名称不能为空: {}", jName);
throw new ApiException(I18nUtil._("任务名称不能为空!"));
}
if (jGroup == null || jGroup.trim().isEmpty()) {
logger.warn("[Quartz] 任务组不能为空: {}", jGroup);
throw new ApiException(I18nUtil._("任务组不能为空!"));
}
if (tName == null || tName.trim().isEmpty()) {
logger.warn("[Quartz] 触发器名称不能为空: {}", tName);
throw new ApiException(I18nUtil._("触发器名称不能为空!"));
}
if (tGroup == null || tGroup.trim().isEmpty()) {
logger.warn("[Quartz] 触发器组不能为空: {}", tGroup);
throw new ApiException(I18nUtil._("触发器组不能为空!"));
}
if (cron == null || cron.trim().isEmpty()) {
logger.warn("[Quartz] cron表达式不能为空: {}", cron);
throw new ApiException(I18nUtil._("cron表达式不能为空"));
}
if (cName == null || cName.trim().isEmpty()) {
logger.warn("[Quartz] 任务实现类名称不能为空: {}", cName);
throw new ApiException(I18nUtil._("任务实现类名称不能为空!"));
}
// 2. 加载任务类
Class<Job> clazz;
try {
clazz = (Class<Job>) Class.forName(PATH_PREFIX + cName);
String fullClassName = PATH_PREFIX + cName;
logger.debug("[Quartz] 尝试加载任务类: {}", fullClassName);
clazz = (Class<Job>) Class.forName(fullClassName);
logger.debug("[Quartz] 成功加载任务类: {}", fullClassName);
} catch (ClassNotFoundException e) {
throw new ApiException(I18nUtil._("定时任务脚本不存在!"));
logger.error("[Quartz] 定时任务脚本不存在!类名: {}{}", PATH_PREFIX, cName, e);
throw new ApiException(I18nUtil._("定时任务脚本不存在!类名: " + PATH_PREFIX + cName), e);
}
try {
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jName, jGroup).build();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(tName, tGroup).startNow().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
scheduler.start();
// 3. 构建任务详情和触发器
JobKey jobKey = new JobKey(jName, jGroup);
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity(jobKey)
.build();
TriggerKey triggerKey = new TriggerKey(tName, tGroup);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
logger.debug("[Quartz] 构建任务详情和触发器完成: jobKey={}, triggerKey={}", jobKey, triggerKey);
// 4. 检查任务是否已存在
if (scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务已存在,先删除旧任务: jobKey={}", jobKey);
scheduler.deleteJob(jobKey);
}
// 5. 调度任务
logger.info("[Quartz] 开始调度任务: jobKey={}, triggerKey={}", jobKey, triggerKey);
scheduler.scheduleJob(jobDetail, trigger);
logger.info("[Quartz] 任务调度成功: jobKey={}, triggerKey={}", jobKey, triggerKey);
// 6. 启动调度器如果尚未启动
if (!scheduler.isStarted()) {
logger.info("[Quartz] 调度器尚未启动,正在启动...");
scheduler.start();
logger.info("[Quartz] 调度器启动成功");
}
} catch (SchedulerException e) {
logger.error("[Quartz] 添加定时任务失败!任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}",
jName, jGroup, tName, tGroup, e);
throw new ApiException(I18nUtil._("添加定时任务失败!"), e);
} catch (Exception e) {
throw new ApiException(I18nUtil._("添加定时任务失败!"));
logger.error("[Quartz] 添加定时任务时发生未知异常!任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}",
jName, jGroup, tName, tGroup, e);
throw new ApiException(I18nUtil._("添加定时任务失败!"), e);
}
}
/**
* 添加或更新任务如果调度器中不存在任务则从数据库获取信息添加
*
* @param jobName 任务名称
* @param jobGroup 任务组
* @param triggerName 触发器名称
* @param triggerGroup 触发器组
* @param dbCron 从数据库获取的cron表达式
* @param dbJobClass 从数据库获取的任务类名
* @return 是否添加成功
*/
public boolean addOrUpdateJobFromDatabase(String jobName, String jobGroup, String triggerName,
String triggerGroup, String dbCron, String dbJobClass) {
logger.info("[Quartz] 尝试从数据库信息添加或更新任务: 任务名称={}, 任务组={}", jobName, jobGroup);
try {
JobKey jobKey = new JobKey(jobName, jobGroup);
// 检查任务是否已存在
if (scheduler.checkExists(jobKey)) {
logger.debug("[Quartz] 任务已存在于调度器中: jobKey={}", jobKey);
return true; // 任务已存在无需添加
}
// 任务不存在从数据库信息创建新任务
logger.info("[Quartz] 调度器中不存在任务,从数据库信息创建: jobKey={}", jobKey);
addJob(jobName, jobGroup, triggerName, triggerGroup, dbCron, dbJobClass);
return true;
} catch (Exception e) {
logger.error("[Quartz] 从数据库信息添加或更新任务失败: 任务名称={}, 任务组={}", jobName, jobGroup, e);
return false;
}
}
/**
* 暂停定时任务
*
@ -53,10 +157,24 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void pauseJob(String jName, String jGroup) {
logger.info("[Quartz] 开始暂停定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需暂停: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
scheduler.pauseJob(jobKey);
logger.info("[Quartz] 定时任务暂停成功: jobKey={}", jobKey);
} catch (SchedulerException e) {
logger.error("[Quartz] 暂停定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使暂停失败也继续执行避免阻塞业务流程
} catch (Exception e) {
throw new ApiException(I18nUtil._("暂停定时任务失败!"));
logger.error("[Quartz] 暂停定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使暂停失败也继续执行避免阻塞业务流程
}
}
@ -68,10 +186,24 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void resumeJob(String jName, String jGroup) {
logger.info("[Quartz] 开始继续定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需继续: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
scheduler.resumeJob(jobKey);
logger.info("[Quartz] 定时任务继续成功: jobKey={}", jobKey);
} catch (SchedulerException e) {
throw new ApiException(I18nUtil._("继续定时任务失败!"));
logger.error("[Quartz] 继续定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使继续失败也继续执行避免阻塞业务流程
} catch (Exception e) {
logger.error("[Quartz] 继续定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使继续失败也继续执行避免阻塞业务流程
}
}
@ -83,10 +215,30 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void deleteJob(String jName, String jGroup) {
logger.info("[Quartz] 开始删除定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需删除: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
boolean deleted = scheduler.deleteJob(jobKey);
if (deleted) {
logger.info("[Quartz] 定时任务删除成功: jobKey={}", jobKey);
} else {
logger.warn("[Quartz] 定时任务删除失败: jobKey={}", jobKey);
}
} catch (SchedulerException e) {
throw new ApiException(I18nUtil._("删除定时任务失败!"));
logger.error("[Quartz] 删除定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使删除失败也继续执行避免阻塞业务流程
} catch (Exception e) {
logger.error("[Quartz] 删除定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使删除失败也继续执行避免阻塞业务流程
}
}
}

View File

@ -1,12 +1,51 @@
package com.suisung.mall.shop.config;
import com.suisung.mall.core.config.BaseRedisConfig;
import com.suisung.mall.shop.order.listener.RedisKeyExpiredListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
/**
* Redis相关配置
*/
@Slf4j
@Configuration
public class RedisConfig extends BaseRedisConfig {
@Bean
RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory,
MessageListenerAdapter keyExpiredListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(keyExpiredListener, new PatternTopic("__keyevent@*__:expired"));
return container;
}
@Bean
MessageListenerAdapter keyExpiredListener(RedisKeyExpiredListener redisKeyExpiredListener) {
return new MessageListenerAdapter(redisKeyExpiredListener);
}
@Bean
public RedisKeyExpiredListener redisKeyExpiredListener() {
return new RedisKeyExpiredListener();
}
// /**
// * 监听Redis键过期事件
// */
// public static class KeyExpiredListener implements MessageListener {
// @Override
// public void onMessage(org.springframework.data.redis.connection.Message message,
// byte[] pattern) {
// String expiredKey = new String(message.getBody());
// // 在这里处理过期键的逻辑
// log.info("Redis 过期事件监听 Key expired: {}", expiredKey);
// // 您可以在这里添加自己的业务逻辑比如清理相关资源发送通知等
// }
// }
}

View File

@ -310,7 +310,7 @@ public class EsignContractServiceImpl extends BaseServiceImpl<EsignContractMappe
if (success && StrUtil.isNotBlank(downloadUrl)) {
// 1电子合同给商家申请分账功能使用务必检查是否申请过申请过忽略
Pair<Boolean, String> retPair = lakalaApiService.innerApplyLedgerMer(esignContract.getMch_mobile());
Pair<Boolean, String> retPair = lakalaApiService.innerApplyLedgerMer("", false);
if (!retPair.getFirst()) {
log.error("商家申请分账业务异常:{}", retPair.getSecond());
}

View File

@ -0,0 +1,42 @@
/*
* 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.controller.admin;
import cn.hutool.json.JSONObject;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.service.impl.BaseControllerImpl;
import com.suisung.mall.shop.lakala.service.LakalaApiService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Api(tags = "拉卡拉相关接口 - 后端控制器")
@RestController
@RequestMapping("/admin/shop/lakala")
public class LakalaAdminController extends BaseControllerImpl {
@Resource
private LakalaApiService lakalaPayService;
@ApiOperation(value = "查询拉卡拉商户可分账的金额", notes = "查询拉卡拉商户可分账的金额")
@RequestMapping(value = "/sacs/queryMchSplitAmt", method = RequestMethod.POST)
public CommonResult queryMchCanSplitAmt(@RequestBody JSONObject paramsJSON) {
JSONObject resultJSON = lakalaPayService.queryMchCanSplitAmtInner(paramsJSON.getStr("merchantNo"), paramsJSON.getStr("logNo"), paramsJSON.getStr("logDate"));
if (resultJSON != null) {
return CommonResult.success(resultJSON);
}
return CommonResult.failed();
}
}

View File

@ -13,6 +13,7 @@ import cn.hutool.json.JSONUtil;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.service.impl.BaseControllerImpl;
import com.suisung.mall.shop.lakala.service.LakalaApiService;
import com.suisung.mall.shop.lakala.service.LklLedgerEcService;
import com.suisung.mall.shop.library.service.LibraryProductService;
import com.suisung.mall.shop.message.service.MqMessageService;
import com.suisung.mall.shop.message.service.PushMessageService;
@ -28,6 +29,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@ -71,6 +73,13 @@ public class LakalaController extends BaseControllerImpl {
@Resource
private MqMessageService mqMessageService;
@Lazy
@Resource
private LakalaApiService lakalaApiService;
@Resource
private LklLedgerEcService lklLedgerEcService;
@ApiOperation(value = "测试案例", notes = "测试案例")
@RequestMapping(value = "/testcase", method = RequestMethod.POST)
public Object testcase(@RequestBody JSONObject paramsJSON) {
@ -105,8 +114,9 @@ public class LakalaController extends BaseControllerImpl {
// return shopOrderBaseService.sameCityOrderExpireSeconds(10000L);
return sfExpressApiService.createSfExpressShop(66, "能辉超市", "桂平市", "广西壮族自治区贵港市桂平市广佰汇超市(桂平店)", "谢能坤", "17777525395", "110.07165452271", "23.369069486251");
// return sfExpressApiService.createSfExpressShop(66, "能辉超市", "桂平市", "广西壮族自治区贵港市桂平市广佰汇超市(桂平店)", "谢能坤", "17777525395", "110.07165452271", "23.369069486251");
return lakalaApiService.sacsQuery("8226330541100GU", "20250918770188017227140800").toString();
}
@ApiOperation(value = "批量发送推送消息 - 测试案例", notes = "批量发送推送消息 - 测试案例")
@ -131,6 +141,18 @@ public class LakalaController extends BaseControllerImpl {
return lakalaPayService.getBankCardBin(paramsJSON.getStr("bankCardNo"));
}
@ApiOperation(value = "发货类交易确认收货通知", notes = "发货类交易确认收货通知 https://o.lakala.com/#/home/document/detail?id=1003")
@RequestMapping(value = "/trans/receive/completeNotify", method = RequestMethod.POST)
public ResponseEntity<JSONObject> receiveCompleteNotify(HttpServletRequest request) {
// 完整地址 https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify
JSONObject resp = lakalaPayService.receiveCompleteNotify(request);
if (resp != null && "SUCCESS".equals(resp.get("code"))) {
return ResponseEntity.ok(resp);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
}
@ApiOperation(value = "商户入网电子合同申请回调通知", notes = "商户入网电子合同申请回调通知")
@RequestMapping(value = "/ec/applyNotify", method = RequestMethod.POST)
public ResponseEntity<JSONObject> ecApplyNotify(HttpServletRequest request) {
@ -142,6 +164,19 @@ public class LakalaController extends BaseControllerImpl {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
}
@ApiOperation(value = "跳转到拉卡拉签署合同链接", notes = "跳转到拉卡拉签署合同链接")
@RequestMapping(value = "/sign/ec/{code}", method = RequestMethod.GET)
public ModelAndView jumpToSignLklEcLink(@PathVariable Long code) {
// 根据 code 值获取结果 URL
String resultUrl = lklLedgerEcService.getLklEcResultUrl(code);
ModelAndView modelAndView = new ModelAndView("sign_lkl_ec");
modelAndView.addObject("resultUrl", resultUrl);
// 返回模板名称渲染 sign_lkl_ec.html 页面
return modelAndView;
}
@ApiOperation(value = "商户分账业务开通申请", notes = "商户分账业务开通申请")
@RequestMapping(value = "/ledger/applyLedgerMer", method = RequestMethod.POST)
public CommonResult ledgerApplyLedgerMer(@RequestBody JSONObject paramsJSON) {
@ -182,4 +217,24 @@ public class LakalaController extends BaseControllerImpl {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
}
/**
* 分账结果通知
* 参考https://o.lakala.com/#/home/document/detail?id=393
*
* @param request
* @return
*/
@ApiOperation(value = "分账关系绑定申请异步回调通知", notes = "分账关系绑定申请异步回调通知")
@RequestMapping(value = "/sacs/separateNotify", method = RequestMethod.POST)
public ResponseEntity<JSONObject> separateNotify(HttpServletRequest request) {
JSONObject resp = lakalaPayService.sacsSeparateNotify(request, "", "");
if (resp != null && "SUCCESS".equals(resp.get("code"))) {
return ResponseEntity.ok(resp);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
}
}

View File

@ -8,15 +8,19 @@
package com.suisung.mall.shop.lakala.controller.mobile;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.service.impl.BaseControllerImpl;
import com.suisung.mall.shop.lakala.service.LakalaApiService;
import com.suisung.mall.shop.lakala.service.LklBanksService;
import com.suisung.mall.shop.lakala.service.impl.LklTkServiceImpl;
import com.suisung.mall.shop.lakala.utils.LakalaUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -27,6 +31,7 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Slf4j
@Api(tags = "拉卡拉商户进件控制器")
@RestController
@RequestMapping("/mobile/shop/lakala/tk")
@ -38,6 +43,10 @@ public class LklTkController extends BaseControllerImpl {
@Resource
private LklBanksService lklBanksService;
@Lazy
@Resource
private LakalaApiService lakalaPayService;
@ApiOperation(value = "解密拉卡拉进件返回的异步通知", notes = "解密拉卡拉进件返回的异步通知")
@RequestMapping(value = "/decode", method = RequestMethod.POST)
public String decryptLklTkData(@RequestParam(name = "data") String data,
@ -50,8 +59,33 @@ public class LklTkController extends BaseControllerImpl {
@ApiOperation(value = "搜索国内银行(支行)分页列表", notes = "搜索国内银行(支行)分页列表,数据包含有效的收款结清行号")
@RequestMapping(value = "/bank/search", method = RequestMethod.POST)
public CommonResult searchLklBanksPageList(@RequestBody JSONObject paramsJSON) {
// Page<LklBanks> list = lklBanksService.searchBranchBanksPageList(paramsJSON.getStr("keyword"), paramsJSON.getInt("pageNum"), paramsJSON.getInt("pageSize"));
IPage<Map> list = lklBanksService.pageBranchBanksList("", "", 2, paramsJSON.getStr("keyword"), paramsJSON.getInt("pageNum"), paramsJSON.getInt("pageSize"));
log.debug("开始搜索国内银行(支行)分页列表,参数: {}", paramsJSON);
String clearNo = "";
String bankCardNo = paramsJSON.getStr("bankCardNo");
String keyword = paramsJSON.getStr("keyword");
Integer pageNum = paramsJSON.getInt("pageNum");
Integer pageSize = paramsJSON.getInt("pageSize");
log.debug("搜索参数: bankCardNo={}, keyword={}, pageNum={}, pageSize={}", bankCardNo, keyword, pageNum, pageSize);
// 参数校验
if (StrUtil.isBlank(bankCardNo)) {
log.warn("银行卡号为空,无法获取银行信息");
} else {
// 通过卡号获取银行信息
CommonResult result = lakalaPayService.getBankCardBin(bankCardNo);
if (result != null && result.getStatus() == 200L && result.getData() != null) {
JSONObject bankInfo = (JSONObject) result.getData();
clearNo = bankInfo.getStr("clearingBankCode", bankInfo.getStr("bankCode"));
log.debug("获取到银行清算行号: {}", clearNo);
} else {
log.warn("获取银行卡BIN信息失败result={}", result);
}
}
IPage<Map> list = lklBanksService.pageBranchBanksList("", "", clearNo, 2, keyword, pageNum, pageSize);
log.info("搜索国内银行(支行)分页列表完成,结果数量: {}", list != null ? list.getTotal() : 0);
return CommonResult.success(list);
}

View File

@ -22,5 +22,5 @@ import java.util.Map;
@Repository
public interface LklBanksMapper extends BaseMapper<LklBanks> {
IPage<Map> pageBranchBanksList(Page<Map> page, @Param("bankNo") String bankNo, @Param("branchBankNo") String branchBankNo, @Param("type") Integer type, @Param("keywords") List<String> keywords);
IPage<Map> pageBranchBanksList(Page<Map> page, @Param("bankNo") String bankNo, @Param("branchBankNo") String branchBankNo, @Param("clearNo") String clearNo, @Param("type") Integer type, @Param("keywords") List<String> keywords);
}

View File

@ -90,10 +90,24 @@ public interface LakalaApiService {
/**
* 内部调用商户分账业务开通申请
*
* @param merCupNo
* @param merCupNo 商户号
* @param forceReApply 如果已申请成功是否可以重新申请更改
* @return
*/
Pair<Boolean, String> innerApplyLedgerMer(String merCupNo);
Pair<Boolean, String> innerApplyLedgerMer(String merCupNo, Boolean forceReApply);
/**
* 发货类交易确认收货通知
* 参考https://o.lakala.com/#/home/document/detail?id=1003
* 注意
* 1交易通知接口是交易成功完成后会向 complete_notify_url 这个地址主扫交易或者主扫合单请求中的complete_notify_url字段发起交易结果通知拉卡拉系统通知时如果商户的应答没有按照以下响应参考报文示例返回成功状态时则系统认为通知失败系统会通过一定的策略定期重新发起通知
* 2同样的通知可能会多次发送给商户系统商户系统必须能够正确处理重复的通知
* 3在没有收到拉卡拉支付交易通知的情况下建议商户主动调用06查询交易确认交易状态
*
* @param request
* @return
*/
JSONObject receiveCompleteNotify(HttpServletRequest request);
/**
* 商户入网电子合同申请回调通知
@ -172,14 +186,39 @@ public interface LakalaApiService {
*/
Pair<Boolean, String> innerDoOrderSeparate(String orderId, String storeId);
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>
* 该方法用于处理拉卡拉订单的分账逻辑在用户确认收货后系统会根据订单信息执行分账操作
* 将订单金额按照预设比例分配给平台商家和代理商如果存在
* </p>
* <p>
* 分账操作流程
* 1. 参数校验和订单查询
* 2. 检查订单状态是否已确认收货
* 3. 检查是否已分账避免重复处理
* 4. 计算分账金额
* 5. 构建分账请求并发送至拉卡拉
* 6. 保存分账结果
* </p>
*
* @param lklMerchantNo 拉卡拉商户号
* @param receiveTradeNo 收货交易号对应拉卡拉的trade_no
* @param receiveLogNo 收货流水号对应拉卡拉的log_no
* @return Pair<Boolean, String> 处理结果对first为是否成功second为结果描述信息
*/
Pair<Boolean, String> innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo);
/**
* 分账结果通知
* 参考https://o.lakala.com/#/home/document/detail?id=393
*
* @param request
* @param merchantNo 分账方商户号非空表示是事后补偿
* @param separateNo 分账指令流水号非空表示是事后补偿
* @return
*/
JSONObject sacsSeparateNotify(HttpServletRequest request);
JSONObject sacsSeparateNotify(HttpServletRequest request, String merchantNo, String separateNo);
/**
* 查询拉卡拉商户可分账的金额
@ -199,6 +238,36 @@ public interface LakalaApiService {
*/
Pair<String, String> queryMchCanSplitAmt(String merchantNo, String logNo, String logDate);
/**
* 查询拉卡拉商户可分账的金额
* 参考https://o.lakala.com/#/home/document/detail?id=394
*
* @param merchantNo 拉卡拉外部商户号
* @param logNo 拉卡拉对账单流水号
* @param logDate 拉卡拉对账单交易日期 yyyyMMdd
* @return 响应结果 {
* "merchant_no": "82229005943096D",
* "total_separate_amt": "9900",
* "can_separate_amt": "0",
* "log_date": "20221220",
* "log_no": "66210306990190"
* }
*/
JSONObject queryMchCanSplitAmtInner(String merchantNo, String logNo, String logDate);
/**
* 分账查询结果
* <p>
* 用于查询已发起的分账交易的处理结果状态
* 参考https://o.lakala.com/#/home/document/detail?id=392
*
* @param merchantNo 分账方商户号
* @param separateNo 分账指令流水号
* @return 分账结果数据包含分账状态等信息查询失败时返回null
*/
JSONObject sacsQuery(String merchantNo, String separateNo);
/**
* 订单分账撤销
* 参考https://o.lakala.com/#/home/document/detail?id=390
@ -212,4 +281,11 @@ public interface LakalaApiService {
*/
Boolean sacsSeparateCancel(String merchantNo, String originSeparateNo, String originOutSeparateNo, String outSeparateNo, String totalAmt);
/**
* 更改已经分账的状态定时任务用途
*
* @return
*/
Integer fixedUnSuccessSeparateStatusJob();
}

View File

@ -32,13 +32,14 @@ public interface LklBanksService {
*
* @param bankNo 总行号
* @param branchBankNo 支行号
* @param clearNo 清算行号
* @param type 1-店铺地区2-银行地区
* @param keyword 查询关键字,做分词
* @param pageNum
* @param pageSize
* @return
*/
IPage<Map> pageBranchBanksList(String bankNo, String branchBankNo, Integer type, String keyword, Integer pageNum, Integer pageSize);
IPage<Map> pageBranchBanksList(String bankNo, String branchBankNo, String clearNo, Integer type, String keyword, Integer pageNum, Integer pageSize);
/**
* 根据支行号查询支行信息带省市地区

View File

@ -50,4 +50,12 @@ public interface LklLedgerEcService extends IBaseService<LklLedgerEc> {
LklLedgerEc getByMchId(Long mchId, String ecStatus, Integer status);
/**
* 获取拉卡拉商户签署合同页面URL
*
* @param mchId
* @return
*/
String getLklEcResultUrl(Long mchId);
}

View File

@ -57,7 +57,7 @@ public interface LklLedgerMerReceiverBindService extends IBaseService<LklLedgerM
* @param applyId
* @return
*/
LklLedgerMerReceiverBind getPlatformByApplyId(String applyId);
LklLedgerMerReceiverBind getMerReceiverByApplyId(String applyId);
/**
* 根据商户编号查询一条代理商绑定记录
@ -91,4 +91,22 @@ public interface LklLedgerMerReceiverBindService extends IBaseService<LklLedgerM
* @return
*/
Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String receiverNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark);
/**
* 根据入驻号和商户编号查询商户分账接收方绑定记录
*
* @param mchId 商户ID
* @param merCupNo 商户银联编号
* @return 删除成功返回true失败返回false
*/
List<LklLedgerMerReceiverBind> selectByMchIdAndMerCupNo(Long mchId, String merCupNo);
/**
* 根据入驻编号和商户编号删除商户分账接收方绑定记录
*
* @param mchId
* @param merCupNo
* @return
*/
Boolean delByMchIdAndMerCupNo(Long mchId, String merCupNo);
}

View File

@ -11,6 +11,9 @@ package com.suisung.mall.shop.lakala.service;
import com.suisung.mall.common.modules.lakala.LklOrderSeparate;
import com.suisung.mall.core.web.service.IBaseService;
import java.util.Date;
import java.util.List;
public interface LklOrderSeparateService extends IBaseService<LklOrderSeparate> {
/**
@ -28,7 +31,7 @@ public interface LklOrderSeparateService extends IBaseService<LklOrderSeparate>
* @param outSeparateNo
* @return
*/
LklOrderSeparate getByOutTradeNo(String logNo, String outSeparateNo);
LklOrderSeparate getByLogNoAndOutTradeNo(String logNo, String outSeparateNo);
/**
* 根据商户号和平台订单号查询记录
@ -47,4 +50,36 @@ public interface LklOrderSeparateService extends IBaseService<LklOrderSeparate>
* @return
*/
Boolean updateRemark(Long id, String remark);
/**
* 根据唯一组合键 logNo和 separateNo 修改备注
*
* @param logNo
* @param separateNo
* @param remark
* @return
*/
Boolean updateRemark(String logNo, String separateNo, String remark);
/**
* 分页获取未成功分账的记录
*
* @param beginDate 开始时间必须
* @param endDate 结束时间可选为空时默认为当前时间
* @param page 页码从1开始
* @param size 每页大小
* @return 未成功分账的记录列表参数无效时返回空列表而非null
*/
List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer size);
/**
* 判断订单是否已经分账
* 记录不存在或 status final_status 不是 SUCCESS 都表示没有分账
*
* @param orderId
* @return
*/
Boolean isOrderSeparated(String orderId);
}

View File

@ -83,6 +83,7 @@ public class LklBanksServiceImpl extends BaseServiceImpl<LklBanksMapper, LklBank
/**
* @param bankNo
* @param branchBankNo
* @param clearNo
* @param type
* @param keyword
* @param pageNum
@ -90,9 +91,9 @@ public class LklBanksServiceImpl extends BaseServiceImpl<LklBanksMapper, LklBank
* @return
*/
@Override
public IPage<Map> pageBranchBanksList(String bankNo, String branchBankNo, Integer type, String keyword, Integer pageNum, Integer pageSize) {
public IPage<Map> pageBranchBanksList(String bankNo, String branchBankNo, String clearNo, Integer type, String keyword, Integer pageNum, Integer pageSize) {
Page<Map> page = new Page<>(pageNum, pageSize);
return lklBanksMapper.pageBranchBanksList(page, bankNo, branchBankNo, type, jiebaUtils.segmentForSearch(keyword));
return lklBanksMapper.pageBranchBanksList(page, bankNo, branchBankNo, clearNo, type, jiebaUtils.segmentForSearch(keyword));
}
/**
@ -107,7 +108,7 @@ public class LklBanksServiceImpl extends BaseServiceImpl<LklBanksMapper, LklBank
return null;
}
IPage<Map> list = lklBanksMapper.pageBranchBanksList(new Page<>(1, 1), null, branchBankNo, 2, null);
IPage<Map> list = lklBanksMapper.pageBranchBanksList(new Page<>(1, 1), null, branchBankNo, "", 2, null);
if (list == null || CollectionUtil.isEmpty(list.getRecords())) {
return null;
}

View File

@ -135,4 +135,19 @@ public class LklLedgerEcServiceImpl extends BaseServiceImpl<LklLedgerEcMapper, L
return findOne(queryWrapper);
}
/**
* 获取拉卡拉商户签署合同页面URL
*
* @param mchId
* @return
*/
@Override
public String getLklEcResultUrl(Long mchId) {
LklLedgerEc record = getByMchId(mchId, "", null);
if (record != null) {
return record.getResult_url();
}
return "";
}
}

View File

@ -14,41 +14,78 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.lakala.mapper.LklLedgerMerReceiverBindMapper;
import com.suisung.mall.shop.lakala.service.LklLedgerMerReceiverBindService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Slf4j
@Service
public class LklLedgerMerReceiverBindServiceImpl extends BaseServiceImpl<LklLedgerMerReceiverBindMapper, LklLedgerMerReceiverBind> implements LklLedgerMerReceiverBindService {
/**
* 根据接收方编号新增或更新记录
* 根据商户编号和接收方编号新增或更新记录
*
* @param record
* @return
* @param record 分账绑定记录
* @return 操作结果成功返回true失败返回false
*/
@Override
public Boolean addOrUpdateByMerCupNoReceiverNo(LklLedgerMerReceiverBind record) {
if (record == null || StrUtil.isBlank(record.getMer_cup_no()) || StrUtil.isBlank(record.getReceiver_no())) {
// 参数校验
if (record == null || StrUtil.hasBlank(record.getMer_cup_no(), record.getReceiver_no())) {
log.warn("分账绑定记录参数校验失败: record为空或关键字段缺失, mer_cup_no={}, receiver_no={}",
record != null ? record.getMer_cup_no() : "null",
record != null ? record.getReceiver_no() : "null");
return false;
}
// 构造查询条件
QueryWrapper<LklLedgerMerReceiverBind> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mer_cup_no", record.getMer_cup_no())
.eq("receiver_no", record.getReceiver_no())
.eq("audit_status", CommonConstant.Enable);
List<LklLedgerMerReceiverBind> existsRecordList = list(queryWrapper);
if (!CollectionUtil.isEmpty(existsRecordList)
&& existsRecordList.get(0) != null
&& existsRecordList.get(0).getId() > 0) {
// update
record.setId(existsRecordList.get(0).getId());
return updateById(record);
.eq("audit_status", CommonConstant.Enable)
.orderByAsc("id");
// 如果商户ID不为空则增加商户ID查询条件
if (CheckUtil.isNotEmpty(record.getMch_id())) {
queryWrapper.eq("mch_id", record.getMch_id());
}
return add(record);
// 查询已存在的记录
LklLedgerMerReceiverBind existsRecord = findOne(queryWrapper);
// 如果存在有效记录则更新该记录
if (existsRecord != null && existsRecord.getId() != null && existsRecord.getId() > 0) {
log.debug("分账绑定记录已存在,执行更新操作: mer_cup_no={}, receiver_no={}, id={}",
record.getMer_cup_no(), record.getReceiver_no(), existsRecord.getId());
record.setId(existsRecord.getId());
boolean updateResult = updateById(record);
if (updateResult) {
log.info("分账绑定记录更新成功: mer_cup_no={}, receiver_no={}, id={}",
record.getMer_cup_no(), record.getReceiver_no(), record.getId());
} else {
log.error("分账绑定记录更新失败: mer_cup_no={}, receiver_no={}, id={}",
record.getMer_cup_no(), record.getReceiver_no(), record.getId());
}
return updateResult;
}
// 不存在有效记录则新增记录
log.debug("分账绑定记录不存在,执行新增操作: mer_cup_no={}, receiver_no={}",
record.getMer_cup_no(), record.getReceiver_no());
boolean addResult = add(record);
if (addResult) {
log.info("分账绑定记录新增成功: mer_cup_no={}, receiver_no={}",
record.getMer_cup_no(), record.getReceiver_no());
} else {
log.error("分账绑定记录新增失败: mer_cup_no={}, receiver_no={}",
record.getMer_cup_no(), record.getReceiver_no());
}
return addResult;
}
/**
@ -122,7 +159,7 @@ public class LklLedgerMerReceiverBindServiceImpl extends BaseServiceImpl<LklLedg
* @return
*/
@Override
public LklLedgerMerReceiverBind getPlatformByApplyId(String applyId) {
public LklLedgerMerReceiverBind getMerReceiverByApplyId(String applyId) {
if (StrUtil.isBlank(applyId)) {
return null;
}
@ -171,50 +208,106 @@ public class LklLedgerMerReceiverBindServiceImpl extends BaseServiceImpl<LklLedg
/**
* 更新审核结果
*
* @param applyId
* @param merInnerNo
* @param merCupNo
* @param receiverNo
* @param entrustFileName
* @param entrustFilePath
* @param auditStatus
* @param auditStatusText
* @param remark
* @return
* @param applyId 申请ID
* @param merInnerNo 商户内部编号
* @param merCupNo 商户银联编号
* @param receiverNo 接收方编号
* @param entrustFileName 委托文件名
* @param entrustFilePath 委托文件路径
* @param auditStatus 审核状态
* @param auditStatusText 审核状态文本
* @param remark 备注
* @return 更新成功返回true失败返回false
*/
@Override
public Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String receiverNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark) {
if (StrUtil.isBlank(applyId) || StrUtil.isBlank(merCupNo) || StrUtil.isBlank(receiverNo) || StrUtil.isBlank(auditStatus)) {
public Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String receiverNo,
String entrustFileName, String entrustFilePath, String auditStatus,
String auditStatusText, String remark) {
// 检查必需参数是否为空
if (StrUtil.hasBlank(applyId, merCupNo, receiverNo, auditStatus)) {
return false;
}
UpdateWrapper<LklLedgerMerReceiverBind> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("apply_id", applyId);
if (StrUtil.isNotBlank(merInnerNo)) {
updateWrapper.set("mer_inner_no", merInnerNo);
}
if (StrUtil.isNotBlank(merCupNo)) {
updateWrapper.set("mer_cup_no", merCupNo);
}
if (StrUtil.isNotBlank(receiverNo)) {
updateWrapper.set("receiver_no", receiverNo);
}
if (StrUtil.isNotBlank(entrustFileName)) {
updateWrapper.set("entrust_file_name", entrustFileName);
}
if (StrUtil.isNotBlank(entrustFilePath)) {
updateWrapper.set("entrust_file_path", entrustFilePath);
}
if (StrUtil.isNotBlank(auditStatus)) {
updateWrapper.set("audit_status", auditStatus);
}
if (StrUtil.isNotBlank(auditStatusText)) {
updateWrapper.set("audit_status_text", auditStatusText);
}
if (StrUtil.isNotBlank(remark)) {
updateWrapper.set("remark", remark);
}
// 只有当参数不为空时才更新对应字段
updateWrapper.set("mer_inner_no", merInnerNo);
updateWrapper.set("mer_cup_no", merCupNo);
updateWrapper.set("receiver_no", receiverNo);
updateWrapper.set("entrust_file_name", entrustFileName);
updateWrapper.set("entrust_file_path", entrustFilePath);
updateWrapper.set("audit_status", auditStatus);
updateWrapper.set("audit_status_text", auditStatusText);
updateWrapper.set("remark", remark);
return update(updateWrapper);
}
/**
* 根据入驻号和商户编号查询商户分账接收方绑定记录
*
* @param mchId 商户ID
* @param merCupNo 商户银联编号
* @return 符合条件的商户分账接收方绑定记录列表
*/
@Override
public List<LklLedgerMerReceiverBind> selectByMchIdAndMerCupNo(Long mchId, String merCupNo) {
log.debug("开始查询商户分账接收方绑定记录mchId={}, merCupNo={}", mchId, merCupNo);
if (CheckUtil.isEmpty(mchId) && StrUtil.isBlank(merCupNo)) {
log.warn("查询商户分账接收方绑定记录参数校验失败mchId和merCupNo不能同时为空");
return Collections.emptyList();
}
try {
QueryWrapper<LklLedgerMerReceiverBind> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mch_id", mchId)
.eq("mer_cup_no", merCupNo);
List<LklLedgerMerReceiverBind> result = list(queryWrapper);
log.debug("查询商户分账接收方绑定记录完成mchId={}, merCupNo={}, 结果数量={}", mchId, merCupNo, result.size());
return result;
} catch (Exception e) {
log.error("查询商户分账接收方绑定记录时发生异常mchId={}, merCupNo={}", mchId, merCupNo, e);
return Collections.emptyList();
}
}
/**
* 根据入驻号和商户编号删除商户分账接收方绑定记录
*
* @param mchId 商户ID
* @param merCupNo 商户银联编号
* @return 删除成功返回true失败返回false
*/
@Override
public Boolean delByMchIdAndMerCupNo(Long mchId, String merCupNo) {
try {
log.debug("开始删除商户分账接收方绑定记录mchId={}, merCupNo={}", mchId, merCupNo);
if (CheckUtil.isEmpty(mchId) && StrUtil.isBlank(merCupNo)) {
log.warn("删除商户分账接收方绑定记录参数校验失败mchId和merCupNo不能同时为空");
return false;
}
QueryWrapper<LklLedgerMerReceiverBind> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mch_id", mchId).eq("mer_cup_no", merCupNo);
boolean removeResult = remove(queryWrapper);
if (removeResult) {
log.info("商户分账接收方绑定记录删除成功mchId={}, merCupNo={}", mchId, merCupNo);
} else {
log.warn("商户分账接收方绑定记录删除失败或未找到匹配记录mchId={}, merCupNo={}", mchId, merCupNo);
}
return removeResult;
} catch (Exception e) {
log.error("删除商户分账接收方绑定记录时发生异常mchId={}, merCupNo={}", mchId, merCupNo, e);
return false;
}
}
}

View File

@ -137,14 +137,14 @@ public class LklLedgerReceiverServiceImpl extends BaseServiceImpl<LklLedgerRecei
* 通过平台方代理商 ID 记录信息转成 Lakala 接收方记录
* 平台方一定要获取如果有代理商也要获取
*
* @param platformId
* @param distributorId 代理商id非平台Id
* @return
*/
@Override
public JSONArray buildApplyLedgerReceiverReqParams(Long platformId) {
public JSONArray buildApplyLedgerReceiverReqParams(Long distributorId) {
List<Long> ids = new ArrayList<>();
if (platformId != null && platformId > 0) {
ids.add(platformId);
if (distributorId != null && distributorId > 0) {
ids.add(distributorId);
}
// 获取平台记录和代理商记录
List<EsignPlatformInfo> esignPlatformInfoList = esignPlatformInfoService.getDistributorAndPlatformByIds(ids.toArray(new Long[0]));
@ -216,14 +216,14 @@ public class LklLedgerReceiverServiceImpl extends BaseServiceImpl<LklLedgerRecei
* 内部调用申请一个或多个分账接收方平台方和代理商
*
* @param mchId
* @param merCupNo 用于标记接收方绑定merCupNo商家
* @param platformId
* @param merCupNo 用于标记接收方绑定merCupNo商家
* @param distributorId 代理商id非平台id
* @return
*/
@Override
public Boolean innerApplyLedgerReceiver(Long mchId, String merCupNo, Long platformId) {
public Boolean innerApplyLedgerReceiver(Long mchId, String merCupNo, Long distributorId) {
// 接收方至少有一个平台方
JSONArray buildApplyLedgerReceiverReqParams = buildApplyLedgerReceiverReqParams(platformId);
JSONArray buildApplyLedgerReceiverReqParams = buildApplyLedgerReceiverReqParams(distributorId);
if (buildApplyLedgerReceiverReqParams == null || buildApplyLedgerReceiverReqParams.isEmpty()) {
log.error("先新增平台或代理商信息");
return false;

View File

@ -11,6 +11,7 @@ package com.suisung.mall.shop.lakala.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.modules.lakala.LklOrderSeparate;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.lakala.mapper.LklOrderSeparateMapper;
@ -18,6 +19,8 @@ import com.suisung.mall.shop.lakala.service.LklOrderSeparateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@Slf4j
@ -54,12 +57,12 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
/**
* 根据拉卡拉对账单流水号和平台订单号查询记录
*
* @param logNo
* @param outSeparateNo
* @param logNo 分账对账流水号
* @param outSeparateNo 分账商家的订单号
* @return
*/
@Override
public LklOrderSeparate getByOutTradeNo(String logNo, String outSeparateNo) {
public LklOrderSeparate getByLogNoAndOutTradeNo(String logNo, String outSeparateNo) {
try {
if (StrUtil.isBlank(logNo) || StrUtil.isBlank(outSeparateNo)) {
log.warn("查询参数为空logNo={}, outSeparateNo={}", logNo, outSeparateNo);
@ -130,4 +133,121 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
lklOrderSeparate.setRemark(remark);
return updateById(lklOrderSeparate);
}
/**
* 根据唯一组合键 logNo和 separateNo 修改备注
*
* @param logNo
* @param separateNo
* @param remark
* @return
*/
@Override
public Boolean updateRemark(String logNo, String separateNo, String remark) {
if (StrUtil.isEmpty(logNo) || StrUtil.isEmpty(separateNo) || StrUtil.isEmpty(remark)) {
return false;
}
LklOrderSeparate lklOrderSeparate = new LklOrderSeparate();
lklOrderSeparate.setLog_no(logNo);
lklOrderSeparate.setSeparate_no(separateNo);
lklOrderSeparate.setRemark(remark);
return updateById(lklOrderSeparate);
}
/**
* 分页获取未成功分账的记录
*
* @param beginDate 开始时间必须
* @param endDate 结束时间可选为空时默认为当前时间
* @param page 页码从1开始
* @param pageSize 每页大小
* @return 未成功分账的记录列表参数无效时返回空列表而非null
*/
@Override
public List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer pageSize) {
// 1. 参数校验
if (beginDate == null) {
log.warn("[分页查询未成功分账记录] 开始时间不能为空");
return Collections.emptyList();
}
if (page == null || page < 1) {
log.warn("[分页查询未成功分账记录] 页码必须大于0当前页码: {}", page);
return Collections.emptyList();
}
if (pageSize == null || pageSize <= 0) {
log.warn("[分页查询未成功分账记录] 页面大小必须大于0当前大小: {}", pageSize);
return Collections.emptyList();
}
// 2. 处理结束时间默认值
Date actualEndDate = (endDate != null) ? endDate : new Date();
// 3. 参数合理性校验
if (beginDate.after(actualEndDate)) {
log.warn("[分页查询未成功分账记录] 开始时间{}不能晚于结束时间{}", beginDate, actualEndDate);
return Collections.emptyList();
}
try {
// 4. 构造查询条件
QueryWrapper<LklOrderSeparate> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "merchant_no", "separate_no", "status", "final_status", "created_at")
.ne("status", "SUCCESS")
.ne("final_status", "SUCCESS")
.ge("created_at", beginDate)
.le("created_at", actualEndDate)
.orderByDesc("id");
// 5. 执行分页查询
Page<LklOrderSeparate> resultPage = lists(queryWrapper, page, pageSize);
List<LklOrderSeparate> result = resultPage.getRecords();
log.info("[分页查询未成功分账记录] 查询完成,开始时间={},结束时间={},页码={},页面大小={},结果数量={}",
beginDate, actualEndDate, page, pageSize, (result != null ? result.size() : 0));
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
log.error("[分页查询未成功分账记录] 查询过程中发生异常,开始时间={},结束时间={},页码={},页面大小={}",
beginDate, endDate, page, pageSize, e);
return Collections.emptyList();
}
}
/**
* 判断订单是否已经分账完成
*
* @param orderId 订单ID
* @return Boolean true-分账已完成false-分账未完成或不存在分账记录
*/
@Override
public Boolean isOrderSeparated(String orderId) {
// 参数校验
if (StrUtil.isBlank(orderId)) {
log.warn("订单ID为空无法判断分账状态");
return false;
}
try {
// 直接查询分账成功的记录
LklOrderSeparate lklOrderSeparate = findOne(new QueryWrapper<LklOrderSeparate>()
.eq("order_id", orderId)
.eq("status", "SUCCESS")
.eq("final_status", "SUCCESS")
.orderByDesc("id"));
// 如果查询到分账成功的记录返回true
if (lklOrderSeparate == null) {
log.debug("订单[{}]分账未完成或不存在分账记录", orderId);
return false;
}
log.debug("订单[{}]分账已完成", orderId);
return true;
} catch (Exception e) {
log.error("查询订单[{}]分账状态异常", orderId, e);
return false;
}
}
}

View File

@ -22,7 +22,6 @@ import com.suisung.mall.common.modules.store.ShopMchEntry;
import com.suisung.mall.common.pojo.to.AddressParseResultTO;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.AddressUtil;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.RestTemplateHttpUtil;
import com.suisung.mall.common.utils.UploadUtil;
import com.suisung.mall.core.web.service.RedisService;
@ -318,44 +317,54 @@ public class LklTkServiceImpl {
}
/**
* (重要) 请求拉卡拉进件
* TODO 已经进件过了一般不涉及更改企业小微的信息可以不要重新进件了
* <p>
* 参考拉卡拉给的独立文档2拓客SAAS商户管理接口().docx
* (重要) 请求拉卡拉进件
*
* @param mchId 入驻商家自增Id
* @return
* <p>此方法用于向拉卡拉提交商户进件申请包括完整的商户信息和相关附件
* 已经进件过的商户一般不涉及更改企业小微的信息可以不要重新进件
*
* <p>参考拉卡拉给的独立文档2拓客SAAS商户管理接口().docx
*
* @param mchId 入驻商家自增Id不能为空
* @return Pair对象第一个元素表示操作是否成功第二个元素为操作结果信息
*/
public Pair<Boolean, String> registrationMerchant(Long mchId) {
logger.info("开始执行拉卡拉商户进件流程商户ID: {}", mchId);
// 参数校验
if (ObjectUtil.isEmpty(mchId)) {
return Pair.of(false, "入驻商户Id不能为空");
logger.warn("商户进件失败:入驻编号不能为空");
return Pair.of(false, "入驻编号不能为空");
}
String authorization = getLklTkAuthorization();
if (StrUtil.isBlank(authorization)) {
logger.error("商户进件失败获取拉卡拉token失败");
return Pair.of(false, "获取拉卡拉token失败");
}
JSONObject header = new JSONObject();
header.put("Authorization", getLklTkAuthorization());
header.put("Authorization", authorization);
// 获取商家入驻信息组成请求参数
ShopMchEntry shopMchEntry = shopMchEntryService.shopMerchEntryById(mchId);
if (ObjectUtil.isEmpty(shopMchEntry)) {
logger.warn("商户进件失败商家入驻信息不存在商户ID: {}", mchId);
return Pair.of(false, "商家入驻信息不存在");
}
if (!CommonConstant.Enable.equals(shopMchEntry.getHas_ec_signed())
|| StrUtil.isBlank(shopMchEntry.getContract_download_url())) {
logger.warn("商户进件失败商家未签署合同商户ID: {}", mchId);
return Pair.of(false, "商家先签署合同,再来进件!");
}
// 判断是否已经进件过进件过执行下一步操作进件异步通知的相关操作
//密集操作进件审核通过之后要下一步流程操作申请分账业务创建分账接收方
if (CommonConstant.Enable.equals(shopMchEntry.getHas_apply_mer()) && CheckUtil.isNotEmpty(shopMchEntry.getDistributor_id())
&& StrUtil.isAllNotBlank(shopMchEntry.getLkl_mer_cup_no(), shopMchEntry.getLkl_term_no())) {
// 密集操作进件审核通过之后要下一步流程操作申请分账业务创建分账接收方
// && CheckUtil.isNotEmpty(shopMchEntry.getDistributor_id())
if (CommonConstant.Enable.equals(shopMchEntry.getHas_apply_mer())
&& !StrUtil.hasBlank(shopMchEntry.getLkl_mer_cup_no(), shopMchEntry.getLkl_term_no())) {
// 已经进件过了执行下一步操作
logger.info("商户已进件执行后续操作商户ID: {}", mchId);
registrationMerchantAfterHook(mchId, shopMchEntry.getLkl_mer_cup_no(), shopMchEntry.getDistributor_id());
return Pair.of(true, "请勿重复提交,拉卡拉已进件成功了,准备提交分账业务申请!");
}
@ -415,8 +424,7 @@ public class LklTkServiceImpl {
reqJsonBody.put("accountName", shopMchEntry.getAccount_holder_name()); //结算人账户名称
reqJsonBody.put("accountIdCard", larIdCard);//结算法人或小微个人证件号码(身份证)
String settleType = String.format("D%d", 1);
reqJsonBody.put("settleType", settleType); //结算类型0-秒到不分账1-次日结算需要分账
reqJsonBody.put("settleType", "D1"); //结算类型0-秒到不分账1-次日结算需要分账
reqJsonBody.put("settlementType", "AUTOMATIC"); // 结算方式MANUAL:手动结算(结算至拉卡拉APP钱包),AUTOMATIC:自动结算到银行卡,REGULAR:定时结算仅企业商户支持
// 店铺省市区信息
@ -475,60 +483,87 @@ public class LklTkServiceImpl {
reqJsonBody.put("bizContent", bizContent);
// 附件文件相关开始
logger.debug("开始处理商户附件文件商户ID: {}", mchId);
JSONArray attachments = new JSONArray();
if (isQy) {
if (Boolean.TRUE.equals(isQy)) {
JSONObject SETTLE_ID_CARD_FRONT = updatePhoto(shopMchEntry.getLegal_person_id_images(), "FR_ID_CARD_FRONT", false);
if (SETTLE_ID_CARD_FRONT != null) {
attachments.put(SETTLE_ID_CARD_FRONT); // 法人身份证正面
logger.debug("成功添加法人身份证正面图片");
} else {
logger.warn("法人身份证正面图片添加失败");
}
JSONObject SETTLE_ID_CARD_BEHIND = updatePhoto(shopMchEntry.getLegal_person_id_images2(), "FR_ID_CARD_BEHIND", false);
if (SETTLE_ID_CARD_BEHIND != null) {
attachments.put(SETTLE_ID_CARD_BEHIND); // 法人身份证国徽面
logger.debug("成功添加法人身份证国徽面图片");
} else {
logger.warn("法人身份证国徽面图片添加失败");
}
JSONObject BUSINESS_LICENCE = updatePhoto(shopMchEntry.getBiz_license_image(), "BUSINESS_LICENCE", false);
if (BUSINESS_LICENCE != null) {
attachments.put(BUSINESS_LICENCE); // 营业执照
logger.debug("成功添加营业执照图片");
} else {
logger.warn("营业执照图片添加失败");
}
} else {
JSONObject ID_CARD_FRONT = updatePhoto(shopMchEntry.getIndividual_id_images(), "ID_CARD_FRONT", false);
if (ID_CARD_FRONT != null) {
attachments.put(ID_CARD_FRONT); // 身份证正面
logger.debug("成功添加身份证正面图片");
} else {
logger.warn("身份证正面图片添加失败");
}
JSONObject ID_CARD_BEHIND = updatePhoto(shopMchEntry.getIndividual_id_images2(), "ID_CARD_BEHIND", false);
if (ID_CARD_BEHIND != null) {
attachments.put(ID_CARD_BEHIND); // 身份证国徽面
logger.debug("成功添加身份证国徽面图片");
} else {
logger.warn("身份证国徽面图片添加失败");
}
}
JSONObject SHOP_OUTSIDE_IMG = updatePhoto(shopMchEntry.getFront_facade_image(), "SHOP_OUTSIDE_IMG", false);
if (SHOP_OUTSIDE_IMG != null) {
attachments.put(SHOP_OUTSIDE_IMG); // 门店门面图片
logger.debug("成功添加门店门面图片");
} else {
logger.warn("门店门面图片添加失败");
}
JSONObject SHOP_INSIDE_IMG = updatePhoto(shopMchEntry.getEnvironment_image(), "SHOP_INSIDE_IMG", false);
if (SHOP_INSIDE_IMG != null) {
attachments.put(SHOP_INSIDE_IMG); // 门店内部图片
logger.debug("成功添加门店内部图片");
} else {
logger.warn("门店内部图片添加失败");
}
JSONObject BANK_CARD = updatePhoto(shopMchEntry.getBank_image(), "BANK_CARD", false);
if (BANK_CARD != null) {
attachments.put(BANK_CARD); // 银行卡图片
logger.debug("成功添加银行卡图片");
} else {
logger.warn("银行卡图片添加失败");
}
reqJsonBody.put("attchments", attachments);
logger.debug("商户附件文件处理完成,共添加 {} 个附件", attachments.size());
// 附件文件相关结束
String urlPath = "/sit/htkregistration/merchant";
if (isLklProd) {
if (Boolean.TRUE.equals(isLklProd)) {
// 生产环境启用
urlPath = "/registration/merchant";
}
try {
logger.info("进件请求参数:{}", JSONUtil.toJsonStr(reqJsonBody));
logger.info("准备提交拉卡拉进件请求商户ID: {}", mchId);
logger.debug("进件请求参数:{}", JSONUtil.toJsonStr(reqJsonBody));
JSONObject response = RestTemplateHttpUtil.sendLklPost(buildLklTkUrl(urlPath), header, reqJsonBody, JSONObject.class);
logger.debug("拉卡拉进件响应参数:{}", response);
@ -538,6 +573,7 @@ public class LklTkServiceImpl {
|| !"000000".equals(response.getStr("retCode"))) {
String errMsg = response.getStr("retMsg") == null ? "提交拉卡拉进件,出现未知错误" : response.getStr("retMsg");
logger.error("拉卡拉进件失败商户ID: {},错误信息: {}", mchId, errMsg);
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_NOPASS, "提交拉卡拉进件失败:" + errMsg);
return Pair.of(false, "提交拉卡拉进件失败:" + errMsg);
@ -546,19 +582,24 @@ public class LklTkServiceImpl {
// {"merchantNo": "100132349","status": "WAIT_AUDI","state": "1"}
JSONObject rawData = response.getJSONObject("rawData"); // 进件这个接口比较特殊不按照常规返回数据
String lklMerInnerNo = rawData.getStr("merchantNo"); //拉卡拉内部商户号
logger.info("拉卡拉进件成功商户ID: {},拉卡拉商户号: {}", mchId, lklMerInnerNo);
// 表中的内部外部商户号暂时都传同一个内部商户号以便异步通知更改记录
Boolean success = shopMchEntryService.updateMerchEntryLklMerCupNo(mchId, CommonConstant.Disable2, lklMerInnerNo, lklMerInnerNo, reqJsonBody.toString(), rawData.toString());
if (!success) {
if (!Boolean.TRUE.equals(success)) {
logger.error("进件成功但更新商户号失败商户ID: {}", mchId);
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件成功,但更新商户号失败!");
return Pair.of(false, "提交进件成功,但更新商户号失败!");
}
} catch (Exception e) {
logger.error("拉卡拉进件异常{}", e.getMessage());
logger.error("拉卡拉进件异常商户ID: {}", mchId, e);
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件失败:" + e.getMessage());
return Pair.of(false, "提交拉卡拉进件失败:" + e.getMessage());
}
logger.info("拉卡拉进件提交成功等待审核商户ID: {}", mchId);
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_PADDING, "提交拉卡拉进件成功,正进一步审核!");
return Pair.of(true, "提交拉卡拉进件成功,正进一步审核!");
}
@ -566,91 +607,117 @@ public class LklTkServiceImpl {
/**
* 重要拉卡拉进件异步通知
*
* @param request
* @return
* <p>处理拉卡拉平台发送的商户进件结果异步通知包括解密通知数据更新商户状态等操作
*
* @param request HTTP请求对象包含拉卡拉发送的异步通知数据
* @return JSONObject 响应结果包含处理状态码和消息
*/
// @Transactional
public JSONObject registrationMerchantNotify(HttpServletRequest request) {
logger.debug("拉卡拉进件异步通知开始");
logger.info("开始处理拉卡拉进件异步通知");
// 解密请求参数
String requestBody = LakalaUtil.getBody(request);
logger.debug("拉卡拉进件异步通知返回参数:{}", requestBody);
try {
// 解密请求参数
String requestBody = LakalaUtil.getBody(request);
logger.debug("拉卡拉进件异步通知原始参数:{}", requestBody);
if (StrUtil.isBlank(requestBody)) {
return new JSONObject().set("code", "400").set("message", "返回参数为空");
if (StrUtil.isBlank(requestBody)) {
logger.warn("拉卡拉进件异步通知参数为空");
return new JSONObject().set("code", "400").set("message", "返回参数为空");
}
JSONObject reqBodyJSON = JSONUtil.parseObj(requestBody);
if (reqBodyJSON.isEmpty() || reqBodyJSON.get("data") == null) {
logger.warn("拉卡拉进件异步通知参数格式有误,无法解析: {}", requestBody);
return new JSONObject().set("code", "400").set("message", "参数格式有误,无法解析");
}
String srcData = reqBodyJSON.getStr("data");
if (StrUtil.isBlank(srcData)) {
logger.warn("拉卡拉进件异步通知关键参数为空值");
return new JSONObject().set("code", "400").set("message", "关键参数为空值");
}
// 公钥解密出来的数据
logger.debug("开始解密拉卡拉通知数据");
String notifyPubKey = LakalaUtil.getResourceFile(notifyPubKeyPath, false, false);
String data = LakalaUtil.decryptNotifyData(notifyPubKey, srcData);
if (StrUtil.isBlank(data)) {
logger.error("拉卡拉进件异步通知数据解密失败");
return new JSONObject().set("code", "400").set("message", "数据解密出错!");
}
logger.info("拉卡拉进件异步通知数据解密成功,开始处理业务逻辑");
// 逻辑处理
JSONObject dataJSON = JSONUtil.parseObj(data);
String auditStatus = dataJSON.getStr("status");
String merCupNo = dataJSON.getStr("externalCustomerNo"); //拉卡拉外部商户号
String merInnerNo = dataJSON.getStr("customerNo"); //拉卡拉内部商户号
String termNos = dataJSON.getStr("termNos"); //拉卡拉分配的业务终端号
logger.debug("解析通知数据完成 - 审核状态: {},外部商户号: {},内部商户号: {},终端号: {}",
auditStatus, merCupNo, merInnerNo, termNos);
// 合并参数校验
if (dataJSON.isEmpty() ||
StrUtil.isBlank(auditStatus) ||
StrUtil.isBlank(merCupNo) ||
StrUtil.isBlank(merInnerNo)) {
logger.warn("拉卡拉进件异步通知参数解析出错,数据: {}", data);
return new JSONObject().set("code", "500").set("message", "参数解析出错");
}
// 给商家入驻表增加拉卡拉的商户号和拉卡拉返回的数据
logger.debug("开始查询商户入驻信息,内部商户号: {}", merInnerNo);
ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByMerInnerNo(merInnerNo);
if (shopMchEntry == null) {
logger.error("拉卡拉进件异步通知:{}内部商户号入驻信息不存在!", merInnerNo);
return new JSONObject().put("code", "500").put("message", merInnerNo + "内部商户号入驻信息不存在");
}
Long mchId = shopMchEntry.getId();
logger.info("找到商户入驻信息商户ID: {}", mchId);
// 校验审核状态
logger.debug("校验审核状态: {}", auditStatus);
if (!"SUCCESS".equals(auditStatus) && !"REVIEW_PASS".equals(auditStatus)) {
String remark = dataJSON.getStr("remark");
logger.warn("拉卡拉进件审核未通过,审核状态: {},备注: {}", auditStatus, remark);
shopMchEntryService.updateMerchEntryApprovalByMchId(mchId, CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件失败:" + remark);
return new JSONObject().set("code", "FAIL").set("message", "返回审核状态有误");
}
logger.info("拉卡拉进件审核通过商户ID: {},开始更新商户信息", mchId);
// RMK 拉卡拉进价提交成功,边处理周边的数据边等待审核异步通知
// 更新已进件成功的商户号和设备号
logger.debug("开始更新商户拉卡拉审核状态");
Boolean success = shopMchEntryService.updateMerchEntryLklAuditStatusByLklMerCupNo(
merInnerNo, merCupNo, termNos, CommonConstant.Enable, null, data);
if (!Boolean.TRUE.equals(success)) {
logger.error("拉卡拉进件审核通过但更新商户号失败商户ID: {}", mchId);
shopMchEntryService.updateMerchEntryApprovalByMchId(mchId, CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件时更新商户号失败");
return new JSONObject().set("code", "500").set("message", "更新商户号失败");
}
logger.info("商户拉卡拉审核状态更新成功商户ID: {}", mchId);
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_PADDING,
"进件、申请分账业务、创建分账接收方均已成功,等待拉卡拉审核分账业务请求!");
//密集操作进件审核通过之后要下一步流程操作申请分账业务创建分账接收方
logger.info("开始执行进件后续操作商户ID: {},拉卡拉商户号: {}", mchId, merCupNo);
registrationMerchantAfterHook(mchId, merCupNo, shopMchEntry.getDistributor_id());
logger.info("拉卡拉进件异步通知处理完成商户ID: {}", mchId);
return new JSONObject().set("code", "200").set("message", "处理成功");
} catch (Exception e) {
logger.error("处理拉卡拉进件异步通知异常", e);
return new JSONObject().set("code", "500").set("message", "处理异常:" + e.getMessage());
}
JSONObject reqBodyJSON = JSONUtil.parseObj(requestBody);
if (reqBodyJSON.isEmpty() || reqBodyJSON.get("data") == null) {
return new JSONObject().set("code", "400").set("message", "参数格式有误,无法解析");
}
String srcData = reqBodyJSON.getStr("data");
if (StrUtil.isBlank(srcData)) {
return new JSONObject().set("code", "400").set("message", "关键参数为空值");
}
// 公钥解密出来的数据
String notifyPubKey = LakalaUtil.getResourceFile(notifyPubKeyPath, false, false);
String data = LakalaUtil.decryptNotifyData(notifyPubKey, srcData);
if (StrUtil.isBlank(data)) {
return new JSONObject().set("code", "400").set("message", "数据解密出错!");
}
logger.debug("拉卡拉进件异步通知data解密成功开始处理逻辑");
// 逻辑处理
JSONObject dataJSON = JSONUtil.parseObj(data);
String auditStatus = dataJSON.getStr("status");
String merCupNo = dataJSON.getStr("externalCustomerNo"); //拉卡拉外部商户号
String merInnerNo = dataJSON.getStr("customerNo"); //拉卡拉内部商户号
String termNos = dataJSON.getStr("termNos"); //拉卡拉分配的业务终端号
// 合并参数校验
if (dataJSON.isEmpty() ||
StrUtil.isBlank(auditStatus) ||
StrUtil.isBlank(merCupNo) ||
StrUtil.isBlank(merInnerNo)) {
return new JSONObject().set("code", "500").set("message", "参数解析出错");
}
// 给商家入驻表增加拉卡拉的商户号和拉卡拉返回的数据
ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByMerInnerNo(merInnerNo);
if (shopMchEntry == null) {
logger.error("拉卡拉进件异步通知:{}内部商户号入驻信息不存在!", merInnerNo);
return new JSONObject().put("code", "500").put("message", merInnerNo + "内部商户号入驻信息不存在");
}
Long mchId = shopMchEntry.getId();
// 校验审核状态
if (!"SUCCESS".equals(auditStatus) && !"REVIEW_PASS".equals(auditStatus)) {
logger.debug("返回的审核状态:{}", auditStatus);
shopMchEntryService.updateMerchEntryApprovalByMchId(mchId, CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件失败:" + dataJSON.getStr("remark"));
return new JSONObject().set("code", "FAIL").set("message", "返回审核状态有误");
}
// RMK 拉卡拉进价提交成功,边处理周边的数据边等待审核异步通知
// 更新已进件成功的商户号和设备号
Boolean success = shopMchEntryService.updateMerchEntryLklAuditStatusByLklMerCupNo(
merInnerNo, merCupNo, termNos, CommonConstant.Enable, null, data);
if (!success) {
shopMchEntryService.updateMerchEntryApprovalByMchId(mchId, CommonConstant.MCH_APPR_STA_LKL_NOPASS, "进件时更新商户号失败");
return new JSONObject().set("code", "500").set("message", "更新商户号失败");
}
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), CommonConstant.MCH_APPR_STA_LKL_PADDING,
"进件、申请分账业务、创建分账接收方均已成功,等待拉卡拉审核分账业务请求!");
//密集操作进件审核通过之后要下一步流程操作申请分账业务创建分账接收方
registrationMerchantAfterHook(mchId, merCupNo, shopMchEntry.getDistributor_id());
return new JSONObject().set("code", "200").set("message", "处理成功");
}
/**
@ -664,7 +731,7 @@ public class LklTkServiceImpl {
logger.info("商家进件已成功,下一步申请拉卡拉分账业务,再创建分账接收方!");
// 重要给商家申请分账业务务必检查是否申请过申请过忽略
Pair<Boolean, String> applyRetPair = lakalaApiService.innerApplyLedgerMer(merCupNo);
Pair<Boolean, String> applyRetPair = lakalaApiService.innerApplyLedgerMer(merCupNo, true);
if (!applyRetPair.getFirst()) {
String message = "提交分账业务申请异常:" + applyRetPair.getSecond();
shopMchEntryService.updateMerchEntryApprovalByMchId(mchId, null, message);
@ -857,5 +924,44 @@ public class LklTkServiceImpl {
}
}
public JSONObject bankList(String areaCode, String bankName) {
if (StrUtil.isBlank(areaCode)) {
}
JSONObject header = new JSONObject();
header.put("Authorization", getLklTkAuthorization());
// Base64Utils.encodeToString(file.getBytes());
JSONObject requestBody = new JSONObject();
requestBody.put("areaCode", areaCode);
requestBody.put("bankName", bankName);
String urlPath = "/sit/registration/bank";
if (isLklProd) {
// 生产环境启用
urlPath = "/registration/bank";
}
try {
ResponseEntity<JSONObject> updResponse = RestTemplateHttpUtil.sendPostBodyBackEntity(buildLklTkUrl(urlPath), header, requestBody, JSONObject.class);
if (ObjectUtil.isEmpty(updResponse)
|| updResponse.getStatusCode() != HttpStatus.OK) {
logger.error("上传文件返回值有误");
return null;
}
JSONObject result = updResponse.getBody();
if (ObjectUtil.isEmpty(result)) {
return null;
}
return new JSONObject().put("id", result.get("url")).put("type", "");
} catch (Exception e) {
logger.error("上传文件异常:{}", e.getMessage());
return null;
}
}
}

View File

@ -8,7 +8,10 @@
package com.suisung.mall.shop.lakala.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.lkl.laop.sdk.Config2;
import com.lkl.laop.sdk.LKLSDK;
import com.suisung.mall.common.exception.ApiException;
@ -32,6 +35,7 @@ import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@ -262,6 +266,9 @@ public class LakalaUtil {
try {
log.debug("拉卡拉 Authorization 内容:{}", authorization);
Map<String, String> headerMap = getLakalaAuthorizationMap(authorization);
if (headerMap == null) {
return false;
}
String timestamp = headerMap.get("timestamp");
String nonceStr = headerMap.get("nonce_str");
String signature = headerMap.get("signature");
@ -304,6 +311,12 @@ public class LakalaUtil {
*/
public static Map<String, String> getLakalaAuthorizationMap(String authorization) {
Map<String, String> map = new HashMap();
if (StrUtil.isBlank(authorization)) {
log.error("请求头中无 Authorization 授权信息");
return null;
}
authorization = authorization.trim();
int bpos = authorization.indexOf(" ");
String authType = authorization.substring(0, bpos);
@ -465,14 +478,14 @@ public class LakalaUtil {
if (passVerifySign) {
// 如果不需要验签直接返回请求体内容
String requestBody = getBody(request);
log.debug("###{} 异步回调通知请求过来的参数:{}###", request.getRequestURL(), requestBody);
log.debug("###{} 异步回调通知返回的数据:{}###", request.getRequestURL(), requestBody);
return Pair.of(true, requestBody);
}
// 验签
String authorization = request.getHeader("Authorization");
String requestBody = getBody(request);
log.info("###{} 异步回调通知请求过来的参数:{}\n authorization参数{}###", request.getRequestURL(), requestBody, authorization);
log.info("###{} 异步回调通知返回的数据:{}\n authorization参数{}###", request.getRequestURL(), requestBody, authorization);
boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath);
if (!checkSuccess) {
@ -482,4 +495,31 @@ public class LakalaUtil {
return Pair.of(true, requestBody);
}
/**
* 构建拉卡拉接口公共参数
* <p>
* 公共参数包括:
* - reqTime: 时间戳格式为yyyyMMddHHmmss
* - version: 版本号固定为1.0.0
* - reqId: 请求序列号使用UUID生成唯一标识
*
* @return 包含公共参数的JSONObject
*/
public static JSONObject buildParams() {
JSONObject params = new JSONObject();
// 时间戳格式为yyyyMMddHHmmss
String reqTime = DateUtil.format(new Date(), "yyyyMMddHHmmss");
params.set("reqTime", reqTime);
// 版本号固定为1.0.0
params.set("version", "1.0.0");
// 请求序列号使用UUID生成唯一标识并去除横线
String reqId = IdUtil.simpleUUID();
params.set("reqId", reqId);
return params;
}
}

View File

@ -33,6 +33,7 @@ import java.util.stream.Collectors;
public class PushMessageServiceImpl implements PushMessageService {
private static final String appName = "小发同城";
@Lazy
@Resource
private UniCloudPushService uniCloudPushService;
@ -138,7 +139,7 @@ public class PushMessageServiceImpl implements PushMessageService {
Pair<Boolean, String> result = uniCloudPushService.sendPushMessageBatch(cidList,
appName + "邀请您签署入驻合同",
"恭喜您的开店入驻申请已审核通过!请尽快登录APP平台签署电子合同签署链接24小时内有效逾期需重新提交申请。如有疑问请联系客服感谢您的支持",
"恭喜您的开店入驻申请已审核通过!请尽快登录小发商家 APP 平台签署电子合同签署链接24小时内有效逾期需重新提交申请。如有疑问请联系客服感谢您的支持",
payload);
if (!result.getFirst()) {
@ -167,7 +168,7 @@ public class PushMessageServiceImpl implements PushMessageService {
public void noticeMerchantEmployeeOrderAction(Integer storeId, String orderId, String title, String content, JSONObject payload) {
try {
List<String> cidList = shopStoreEmployeeService.selectEmployeeGeTuiCidByStoreId(storeId, orderId, null);
log.debug("[订单推送] cid 列表:{}", cidList);
// log.debug("[订单推送] cid 列表:{}", cidList);
// 获取 商家的 cid
uniCloudPushService.sendPushMessageBatch(cidList, title, content, payload);
} catch (Exception e) {

View File

@ -38,12 +38,11 @@ import java.util.stream.LongStream;
@lombok.extern.slf4j.Slf4j
public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMapper, ShopNumberSeq> implements ShopNumberSeqService {
private final String CACHE_PREFIX = "shop_number_seq:%S";
@Autowired
private ShopNumberSeqMapper shopNumberSeqMapper;
@Autowired
private RedisService redisService;
private String CACHE_PREFIX = "shop_number_seq:%S";
@Autowired
private ShopProductSpecItemService shopProductSpecItemService;
@ -53,6 +52,10 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
@Autowired
private AccountService accountService;
public static void main(String[] args) {
System.out.printf(IntStream.rangeClosed(1, 1).boxed().collect(Collectors.toList()).toString());
}
/**
* 得到下一个Id
* 方法走到这里会产生串行化集群部署这里不能使用单机锁
@ -65,7 +68,7 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
public synchronized String createNextSeq(String prefix) {
String ymd = DateUtil.format(new Date(), "yyyyMMdd");
String id = String.format("%s-%s-", prefix, ymd);
String id = String.format("%s_%s_", prefix, ymd);
ShopNumberSeq shopNumberSeq = this.baseMapper.selectById(id);
if (shopNumberSeq == null) {
shopNumberSeq = new ShopNumberSeq();
@ -76,7 +79,7 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
}
}
String order_id = String.format("%s-%s-%s", prefix, ymd, shopNumberSeq.getNumber());
String order_id = String.format("%s_%s_%s", prefix, ymd, shopNumberSeq.getNumber());
shopNumberSeq.setPrefix(id);
boolean flag = edit(shopNumberSeq);
if (flag) {
@ -86,7 +89,6 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
return null;
}
/**
* 得到下一个Id
*
@ -128,25 +130,26 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
/**
* 校验同步数据是否同步完成就是两个库的主键是否一致
*
* @return
*/
private void checkPrimaryKey(){
boolean flag = shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size()>1;
while (!flag){
private void checkPrimaryKey() {
boolean flag = shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size() > 1;
while (!flag) {
try {
Thread.sleep(3600);
} catch (InterruptedException e) {
log.error("checkPrimaryKey枷锁失败--"+e.getMessage());
log.error("checkPrimaryKey枷锁失败--" + e.getMessage());
break;
}
flag=shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size()>1;
flag = shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size() > 1;
}
}
/**
* 批量获取id
*
* @param seqName
* @param batchSize
* @return
@ -156,8 +159,8 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
if (batchSize <= 0) {
return Collections.emptyList();
}
long number= 0;
if (null==redisService.get(String.format(CACHE_PREFIX, seqName))) {
long number = 0;
if (null == redisService.get(String.format(CACHE_PREFIX, seqName))) {
// 1. 获取当前序列值
QueryWrapper<ShopNumberSeq> wrapper = new QueryWrapper<>();
wrapper.eq("prefix", seqName);
@ -171,9 +174,9 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
return LongStream.range(1, batchSize + 1).boxed().collect(Collectors.toList());
}
number = seq.getNumber();
}else {
int numberCache=(Integer) redisService.get(String.format(CACHE_PREFIX, seqName))+1;
number= numberCache;
} else {
int numberCache = (Integer) redisService.get(String.format(CACHE_PREFIX, seqName)) + 1;
number = numberCache;
}
@ -187,8 +190,8 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
return LongStream.rangeClosed(start, end).boxed().collect(Collectors.toList());
}
public void batchUpdateSeq(List<ShopNumberSeq> shopNumberSeqList){
int count=0;
public void batchUpdateSeq(List<ShopNumberSeq> shopNumberSeqList) {
int count = 0;
for (int i = 0; i < shopNumberSeqList.size(); i++) {
shopNumberSeqMapper.myUpdateSeq(shopNumberSeqList.get(i));
Long number = shopNumberSeqList.get(i).getNumber();
@ -196,24 +199,24 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
this.edit(shopNumberSeqList.get(i));
count++;
}
log.info("更新成功{}条数据",count);
}
log.info("更新成功{}条数据", count);
}
public void clearKey(){
redisService.del(String.format(CACHE_PREFIX,"item_id"));
redisService.del(String.format(CACHE_PREFIX,"product_id"));
public void clearKey() {
redisService.del(String.format(CACHE_PREFIX, "item_id"));
redisService.del(String.format(CACHE_PREFIX, "product_id"));
}
}
public void clearKeyStoreItemSepcId(){
public void clearKeyStoreItemSepcId() {
redisService.del(RedisKey.STOREDATASPECITEMID);
}
public void clearKeyStoreAccountBaseId(){
public void clearKeyStoreAccountBaseId() {
redisService.del(RedisKey.STOREDATACCOUNTBASEID);
}
public void clearKeyStoreSepcId(){
public void clearKeyStoreSepcId() {
redisService.del(RedisKey.STOREDATASPECID);
}
@ -221,31 +224,32 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
* 清除缓存专门给并发使用,防止redis的缓存没有加载
*/
@Override
public void clearRelateGoodsId(){
boolean flag = shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size()>1;
if(flag){
public void clearRelateGoodsId() {
boolean flag = shopNumberSeqMapper.findNumberFromShopBaseAndItem(new ShopNumberSeq()).size() > 1;
if (flag) {
return;
}
List<ShopNumberSeq> shopNumberSeqList=shopNumberSeqMapper.findProductAndImtemId(new ShopNumberSeq());
for (ShopNumberSeq shopNumberSeq:shopNumberSeqList) {
if(shopNumberSeq.getPrefix().equals("product_id")){
clearCache("product_id",shopNumberSeq.getNumber());
List<ShopNumberSeq> shopNumberSeqList = shopNumberSeqMapper.findProductAndImtemId(new ShopNumberSeq());
for (ShopNumberSeq shopNumberSeq : shopNumberSeqList) {
if (shopNumberSeq.getPrefix().equals("product_id")) {
clearCache("product_id", shopNumberSeq.getNumber());
}
if(shopNumberSeq.getPrefix().equals("item_id")){
clearCache("item_id",shopNumberSeq.getNumber());
if (shopNumberSeq.getPrefix().equals("item_id")) {
clearCache("item_id", shopNumberSeq.getNumber());
}
}
}
}
/**
* 刷新缓存
*/
private void clearCache(String seqName,long number){
private void clearCache(String seqName, long number) {
QueryWrapper<ShopNumberSeq> wrapper = new QueryWrapper<>();
wrapper.eq("prefix", seqName);
ShopNumberSeq seq = getById(seqName);
if (seq != null) {
seq = new ShopNumberSeq();
seq.setNumber(number-1);
seq.setNumber(number - 1);
seq.setPrefix(seqName);
}
edit(seq);
@ -253,88 +257,84 @@ public class ShopNumberSeqServiceImpl extends BaseServiceImpl<ShopNumberSeqMappe
/**
* 存的是最大值取的是范围如存最大值1批量是2则取范围1+11+2,如果没有则从1开始算即取范围[0+1,0+2]
*
* @param batchSize
* @return
*/
@Override
public synchronized List<Integer> getBatchSpecId(int batchSize) {
int start=0;
if(null!=redisService.get(RedisKey.STOREDATASPECID)){
start=(Integer) redisService.get(RedisKey.STOREDATASPECID);
redisService.set(RedisKey.STOREDATASPECID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
int start = 0;
if (null != redisService.get(RedisKey.STOREDATASPECID)) {
start = (Integer) redisService.get(RedisKey.STOREDATASPECID);
redisService.set(RedisKey.STOREDATASPECID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
QueryWrapper<ShopBaseProductSpec> queryWrapper= new QueryWrapper<>();
QueryWrapper<ShopBaseProductSpec> queryWrapper = new QueryWrapper<>();
queryWrapper.select("max(spec_id) as spec_id");
ShopBaseProductSpec shopBaseProductSpec=shopBaseProductSpecService.getOne(queryWrapper);
if(null!=shopBaseProductSpec){
start=shopBaseProductSpec.getSpec_id();
redisService.set(RedisKey.STOREDATASPECID,start+batchSize);
ShopBaseProductSpec shopBaseProductSpec = shopBaseProductSpecService.getOne(queryWrapper);
if (null != shopBaseProductSpec) {
start = shopBaseProductSpec.getSpec_id();
redisService.set(RedisKey.STOREDATASPECID, start + batchSize);
}
if(start==0){
redisService.set(RedisKey.STOREDATASPECID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
if (start == 0) {
redisService.set(RedisKey.STOREDATASPECID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
/**
* 存的是最大值取的是范围如存最大值1批量是2则取范围1+11+2,如果没有则从1开始算即取范围[0+1,0+2]
*
* @param batchSize
* @return
*/
@Override
public synchronized List<Integer> getBatchSpecItemId(int batchSize) {
int start=0;
if(null!=redisService.get(RedisKey.STOREDATASPECITEMID)){
start=(Integer) redisService.get(RedisKey.STOREDATASPECITEMID);
redisService.set(RedisKey.STOREDATASPECITEMID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
int start = 0;
if (null != redisService.get(RedisKey.STOREDATASPECITEMID)) {
start = (Integer) redisService.get(RedisKey.STOREDATASPECITEMID);
redisService.set(RedisKey.STOREDATASPECITEMID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
QueryWrapper<ShopProductSpecItem> queryWrapper= new QueryWrapper<>();
QueryWrapper<ShopProductSpecItem> queryWrapper = new QueryWrapper<>();
queryWrapper.select("max(spec_item_id) as spec_item_id");
ShopProductSpecItem shopProductSpecItem=shopProductSpecItemService.getOne(queryWrapper);
if(null!=shopProductSpecItem){
start=shopProductSpecItem.getSpec_item_id();
redisService.set(RedisKey.STOREDATASPECITEMID,start+batchSize);
ShopProductSpecItem shopProductSpecItem = shopProductSpecItemService.getOne(queryWrapper);
if (null != shopProductSpecItem) {
start = shopProductSpecItem.getSpec_item_id();
redisService.set(RedisKey.STOREDATASPECITEMID, start + batchSize);
}
if(start==0){
redisService.set(RedisKey.STOREDATASPECITEMID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
if (start == 0) {
redisService.set(RedisKey.STOREDATASPECITEMID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
/**
* 存的是最大值取的是范围如存最大值1批量是2则取范围1+11+2,如果没有则从1开始算即取范围[0+1,0+2]
*
* @param batchSize
* @return
*/
@Override
public synchronized List<Integer> getBatchUserAccountBaseId(int batchSize) {
int start=0;
if(null!=redisService.get(RedisKey.STOREDATACCOUNTBASEID)){
start=(Integer) redisService.get(RedisKey.STOREDATACCOUNTBASEID);
redisService.set(RedisKey.STOREDATACCOUNTBASEID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
int start = 0;
if (null != redisService.get(RedisKey.STOREDATACCOUNTBASEID)) {
start = (Integer) redisService.get(RedisKey.STOREDATACCOUNTBASEID);
redisService.set(RedisKey.STOREDATACCOUNTBASEID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
Integer maxId= accountService.getAccountMaxId();
if(null!=maxId){
start=maxId;
redisService.set(RedisKey.STOREDATACCOUNTBASEID,start+batchSize);
Integer maxId = accountService.getAccountMaxId();
if (null != maxId) {
start = maxId;
redisService.set(RedisKey.STOREDATACCOUNTBASEID, start + batchSize);
}
if(start==0){
redisService.set(RedisKey.STOREDATACCOUNTBASEID,start+batchSize);
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
if (start == 0) {
redisService.set(RedisKey.STOREDATACCOUNTBASEID, start + batchSize);
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
return IntStream.rangeClosed(start+1, start+batchSize).boxed().collect(Collectors.toList());
}
public static void main(String[] args) {
System.out.printf(IntStream.rangeClosed(1, 1).boxed().collect(Collectors.toList()).toString());
return IntStream.rangeClosed(start + 1, start + batchSize).boxed().collect(Collectors.toList());
}
}

View File

@ -211,7 +211,7 @@ public class ShopOrderReturnController extends BaseControllerImpl {
* {"order_id":"DD-20250701-1","reason":"商家协商退款","order_return_vo":{"order_id":"DD-20250701-1","return_items":[{"order_item_id":1,"return_item_num":1,"return_refund_amount":"0.01"}]}}
* @return CommonResult 处理结果
*/
@ApiOperation(value = "商家退货退", notes = "商家退货退款,支持整单或个别商品退货")
@ApiOperation(value = "商家退", notes = "商家退款,支持整单或个别商品退货")
@RequestMapping(value = "/mch/order/doRefund", method = RequestMethod.POST)
public CommonResult doRefundForMch(@RequestBody JSONObject params) {
return shopOrderReturnService.doRefundForMch(params);

View File

@ -116,7 +116,8 @@ public class DelayMessageReceiver {
// 根据消息分类处理不同类型的消息
if (MqConstant.DEAD_EVENT_CATE_ORDER_EXPIRED.equals(category)) {
log.info("处理订单超时消息: {}", message);
return handleOrderExpiredMessage(message);
// return handleOrderExpiredMessage(message);
return true;
} else if (MqConstant.DEAD_EVENT_CATE_PRE_ORDER.equals(category)) {
log.info("处理预订单消息: {}", message);
return handlePreOrderMessage(message);

View File

@ -7,9 +7,10 @@ import com.rabbitmq.client.Channel;
import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.constant.MqConstant;
import com.suisung.mall.common.constant.RedisConstant;
import com.suisung.mall.common.modules.order.ShopOrderInfo;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.shop.message.service.MqMessageService;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.shop.message.service.PushMessageService;
import com.suisung.mall.shop.order.service.ShopOrderBaseService;
import com.suisung.mall.shop.order.service.ShopOrderInfoService;
@ -41,6 +42,9 @@ public class OrderPayedListener {
private static final Logger logger = LoggerFactory.getLogger(OrderPayedListener.class);
// 最大重试次数
private static final int MAX_RETRY_COUNT = 5;
@Lazy
@Autowired
private ShopOrderBaseService shopOrderBaseService;
@ -56,7 +60,7 @@ public class OrderPayedListener {
@Lazy
@Autowired
private MqMessageService mqMessageService;
private RedisService redisService;
@RabbitHandler
public void listener(byte[] data, Channel channel, Message message) throws IOException, InterruptedException {
@ -69,53 +73,72 @@ public class OrderPayedListener {
public void listener(String data, Channel channel, Message message) throws IOException, InterruptedException {
// String messageId = message.getMessageProperties().getMessageId();
if (StrUtil.isBlank(data)) {
logger.info("收到空订单消息");
logger.info("[订单支付监听] 收到空订单消息");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
List<String> order_id_row = Convert.toList(String.class, data);
try {
boolean flag = false;
logger.info("收到微信异步通知消息data:{}-chanel:{}-message:{},订单ID:{}", data, channel, message, order_id_row);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
boolean isRedelivered = message.getMessageProperties().isRedelivered();
String retryCountKey = RedisConstant.Order_Pay_Retry_Count_Key + order_id_row.toString();
try {
logger.info("[订单支付监听] 收到微信异步通知消息. 数据: {}, 订单ID: {}, 是否重投: {}", data, order_id_row, isRedelivered);
// 检查重试次数
int retryCount = getRetryCount(retryCountKey);
if (retryCount >= MAX_RETRY_COUNT) {
logger.error("[订单支付监听] 订单处理已达到最大重试次数: {}, 订单ID: {}, 不再处理该消息", MAX_RETRY_COUNT, order_id_row);
channel.basicAck(deliveryTag, false); // 确认消息避免无限重试
redisService.del(retryCountKey); // 清除重试计数
return;
}
boolean flag = false;
for (String orderId : order_id_row) {
//判断是否为线下支付订单
ShopOrderInfo orderInfoOld = shopOrderInfoService.get(orderId);
if (orderInfoOld == null) {
// 记录重试次数
incrementRetryCount(retryCountKey);
logger.warn("[订单支付监听] 订单在数据库中不存在,增加重试计数. 订单ID: {}, 当前重试次数: {}", orderId, retryCount + 1);
continue;
}
// 订单状态
int order_state_id = orderInfoOld.getOrder_state_id().intValue();
logger.info("#### 当前订单状态: {} ####", order_state_id);
logger.info("[订单支付监听] 当前订单状态: {}, 订单ID: {}", order_state_id, orderId);
// 检查订单是否已经处理过幂等性检查
// if (isOrderPaid(orderInfoOld)) {
// logger.info("订单已支付无需重复处理订单ID: {}", orderId);
// flag = true;
// continue;
// }
if (isOrderPaid(orderInfoOld)) {
logger.info("[订单支付监听] 订单已支付无需重复处理订单ID: {}", orderId);
flag = true;
continue;
}
if (order_state_id == StateCode.ORDER_STATE_WAIT_PAY) {
// 待支付状态
logger.info("#### 待支付业务分支 ####");
logger.info("[订单支付监听] 处理待支付订单. 订单ID: {}", orderId);
flag = shopOrderBaseService.setPaidYes(Collections.singletonList(orderId));
} else {
//判断是否线下支付
if (StateCode.PAYMENT_TYPE_OFFLINE == orderInfoOld.getPayment_type_id().intValue()) {
//线下支付直接处理订单支付状态 不处理订单状态
logger.info("#### 线下业务分支 ####");
logger.info("[订单支付监听] 处理线下支付订单. 订单ID: {}", orderId);
ShopOrderInfo orderInfo = new ShopOrderInfo();
orderInfo.setOrder_id(orderId);
orderInfo.setOrder_is_paid(StateCode.ORDER_PAID_STATE_YES);
flag = shopOrderInfoService.edit(orderInfo);
} else {
logger.info("#### 非线下业务分支 ####");
logger.info("[订单支付监听] 处理其他支付订单. 订单ID: {}", orderId);
flag = shopOrderBaseService.setPaidYes(Collections.singletonList(orderId));
}
}
logger.info("#### 支付异步通知回调处理是否成功:{} ####", flag);
logger.info("[订单支付监听] 支付异步通知回调处理结果: {}, 订单ID: {}", flag, orderId);
// 生成取单号和打印小票
if (flag) {
// TODO 以下仅处理下单打印的情况还需要处理退单打印分支
@ -134,16 +157,16 @@ public class OrderPayedListener {
// 发送顺丰同城快递
Pair<Boolean, String> pairCreateSfOrder = sfExpressApiService.innerCreateSfExpressOrder(orderId, orderPickupNum);
if (pairCreateSfOrder == null) {
logger.error("顺丰同城下单失败pairCreateSfOrder 返回空值");
return;
logger.error("[订单支付监听] 顺丰同城下单失败pairCreateSfOrder 返回空值. 订单ID: {}", orderId);
continue;
}
if (!pairCreateSfOrder.getFirst()) {
logger.error("顺丰同城下单失败:{}", pairCreateSfOrder.getSecond());
return;
logger.error("[订单支付监听] 顺丰同城下单失败: {}, 订单ID: {}", pairCreateSfOrder.getSecond(), orderId);
continue;
}
logger.info("顺丰同城下单成功");
logger.info("[订单支付监听] 顺丰同城下单成功. 订单ID: {}", orderId);
}
@ -158,18 +181,16 @@ public class OrderPayedListener {
payload.put("orderId", orderId);
pushMessageService.noticeMerchantEmployeeOrderAction(orderInfoOld.getStore_id(), orderId, title, content, payload);
// 发送 预过期 MQ 的推送消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("category", MqConstant.DEAD_EVENT_CATE_ORDER_EXPIRED); // 消息分类1-订单超时消息
jsonObject.put("orderId", orderId); // 订单ID
jsonObject.put("storeId", orderInfoOld.getStore_id()); // 店铺ID
jsonObject.put("title", "您有一笔将超时的订单。"); // 消息标题
jsonObject.put("message", String.format("您有一笔将超时的订单:%s请您及时处理。", orderId)); // 消息内容
// 发送延迟消息提前10分钟
Long mchOrderExpireSeconds = shopOrderBaseService.sameCityOrderExpireSeconds(1500L) - 600;
mqMessageService.sendDelayMessage(jsonObject.toString(), mchOrderExpireSeconds * 1000); // 转换为毫秒
// 发送延迟消息 25分钟拣货配送时间提前5分钟下单20分钟后提醒商家及时拣货
Long mchOrderExpireSeconds = shopOrderBaseService.sameCityOrderExpireSeconds(1500L) - 300;
redisService.set(RedisConstant.SF_Order_Proc_WillExpire_Key + String.format("%d&%s", orderInfoOld.getStore_id(), orderId), orderId, mchOrderExpireSeconds);
// 发送延迟消息 25分钟拣货配送时间下单25分钟后提醒商家及时拣货
mchOrderExpireSeconds = shopOrderBaseService.sameCityOrderExpireSeconds(1500L);
redisService.set(RedisConstant.SF_Order_Proc_Expire_Key + String.format("%d&%s", orderInfoOld.getStore_id(), orderId), orderId, mchOrderExpireSeconds);
} catch (Exception e) {
log.error("发送推送消息失败:{}", e.getMessage());
log.error("[订单支付监听] 发送推送消息失败. 订单ID: {}", orderId, e);
}
}
}
@ -177,15 +198,22 @@ public class OrderPayedListener {
if (flag) {
// 操作成功标记消息消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
logger.info("[订单支付监听] 消息处理成功,确认消息. 订单ID: {}", order_id_row);
channel.basicAck(deliveryTag, false);
// 清除重试计数
redisService.del(RedisConstant.Order_Pay_Retry_Count_Key + order_id_row);
} else {
log.error("消息消费失败执行setPaidYes异常当前订单编号{}", order_id_row);
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
log.error("[订单支付监听] 消息处理失败执行setPaidYes异常当前订单编号{}", order_id_row);
// 增加重试计数
incrementRetryCount(retryCountKey);
channel.basicReject(deliveryTag, true);
Thread.sleep(1000);
}
} catch (Exception e) {
log.error("消息消费失败执行setPaidYes异常当前订单编号{},失败原因:", order_id_row, e);
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
log.error("[订单支付监听] 消息处理异常,当前订单编号:{},异常信息:", order_id_row, e);
// 增加重试计数
incrementRetryCount(retryCountKey);
channel.basicReject(deliveryTag, true);
Thread.sleep(1000);
}
}
@ -197,12 +225,33 @@ public class OrderPayedListener {
* @return 是否已支付
*/
private boolean isOrderPaid(ShopOrderInfo orderInfo) {
// public static final int ORDER_PAID_STATE_NO = 3010; //未付款
// public static final int ORDER_PAID_STATE_FINANCE_REVIEW = 3011; //待付款审核
// public static final int ORDER_PAID_STATE_PART = 3012; //部分付款
// public static final int ORDER_PAID_STATE_YES = 3013; //已付款
// ORDER_PAID_STATE_NO = 3010; //未付款
// ORDER_PAID_STATE_FINANCE_REVIEW = 3011; //待付款审核
// ORDER_PAID_STATE_PART = 3012; //部分付款
// ORDER_PAID_STATE_YES = 3013; //已付款
return orderInfo.getOrder_is_paid() != null &&
orderInfo.getOrder_is_paid().equals(StateCode.ORDER_PAID_STATE_YES);
orderInfo.getOrder_is_paid().intValue() == StateCode.ORDER_PAID_STATE_YES;
}
/**
* 获取重试次数
*
* @param retryCountKey 重试计数键
* @return 当前重试次数
*/
private int getRetryCount(String retryCountKey) {
Object countObj = redisService.get(retryCountKey);
return countObj == null ? 0 : Convert.toInt(countObj, 0);
}
/**
* 增加重试次数
*
* @param retryCountKey 重试计数键
*/
private void incrementRetryCount(String retryCountKey) {
int currentCount = getRetryCount(retryCountKey);
redisService.set(retryCountKey, currentCount + 1, 3600); // 1小时过期
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.order.listener;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.constant.RedisConstant;
import com.suisung.mall.shop.message.service.PushMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import javax.annotation.Resource;
@Slf4j
public class RedisKeyExpiredListener implements MessageListener {
@Resource
private PushMessageService pushMessageService;
/**
* Callback for processing received objects through Redis.
*
* @param message message must not be {@literal null}.
* @param pattern pattern matching the channel (if specified) - can be {@literal null}.
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = new String(message.getBody());
String patternStr = pattern != null ? new String(pattern) : "无模式";
// 基本日志记录
log.debug("[Redis过期监听] 接收到Redis键过期事件. 过期键: {}, 订阅模式: {}", expiredKey, patternStr);
// 参数验证
if (StrUtil.isBlank(expiredKey)) {
log.warn("[Redis过期监听] 接收到空的过期键. 订阅模式: {}", patternStr);
return;
}
if (expiredKey.startsWith(RedisConstant.SF_Order_Proc_Expire_Key)) {
log.debug("[Redis过期监听] 开始处理订单超时事件. 过期键: {}", expiredKey);
// storeId&orderId
String[] args = expiredKey.replace(RedisConstant.SF_Order_Proc_Expire_Key, "").split("&");
if (ArrayUtil.isEmpty(args) || args.length < 2) {
log.error("[Redis过期监听] 订单处理超时键格式错误. 键: {} 不符合预期格式", expiredKey);
return;
}
log.info("[Redis过期监听] 处理订单超时消息. 店铺ID: {}, 订单号: {}", args[0], args[1]);
boolean result = handleOrderExpiredMessage(args[0], args[1], false);
if (result) {
log.info("[Redis过期监听] 订单超时事件处理成功. 店铺ID: {}, 订单号: {}", args[0], args[1]);
} else {
log.error("[Redis过期监听] 订单超时事件处理失败. 店铺ID: {}, 订单号: {}", args[0], args[1]);
}
} else if (expiredKey.startsWith(RedisConstant.SF_Order_Proc_WillExpire_Key)) {
log.debug("[Redis过期监听] 开始处理订单即将5分钟后超时事件. 过期键: {}", expiredKey);
// storeId&orderId
String[] args = expiredKey.replace(RedisConstant.SF_Order_Proc_WillExpire_Key, "").split("&");
if (ArrayUtil.isEmpty(args) || args.length < 2) {
log.error("[Redis过期监听] 订单处理超时键格式错误. 键: {} 不符合预期格式", expiredKey);
return;
}
log.info("[Redis过期监听] 处理订单即将超时消息. 店铺ID: {}, 订单号: {}", args[0], args[1]);
boolean result = handleOrderExpiredMessage(args[0], args[1], true);
if (result) {
log.info("[Redis过期监听] 订单即将超时事件处理成功. 店铺ID: {}, 订单号: {}", args[0], args[1]);
} else {
log.error("[Redis过期监听] 订单即将超时事件处理失败. 店铺ID: {}, 订单号: {}", args[0], args[1]);
}
} else {
//log.debug("[Redis过期监听] 忽略非订单超时事件. 过期键: {}", expiredKey);
}
}
/**
* 处理订单超时消息
*
* @param storeId 店铺ID
* @param orderId 订单ID
* @param willExpire 此刻还是即将
* @return 处理结果
*/
private boolean handleOrderExpiredMessage(String storeId, String orderId, boolean willExpire) {
try {
String addWord = "";
if (willExpire) {
addWord = "即将";
}
log.debug("[订单{}超时处理] 开始处理订单超时消息. 店铺ID: {}, 订单号: {}", addWord, storeId, orderId);
// 参数验证
if (StrUtil.isBlank(orderId) && StrUtil.isBlank(storeId)) {
log.warn("[订单{}超时处理] 订单ID和店铺ID不能同时为空. 店铺ID: {}, 订单号: {}", addWord, storeId, orderId);
return false;
}
if (StrUtil.isBlank(orderId)) {
log.warn("[订单{}超时处理] 订单号为空. 店铺ID: {}", addWord, storeId);
return false;
}
if (StrUtil.isBlank(storeId)) {
log.warn("[订单{}超时处理] 店铺ID为空. 订单号: {}", addWord, orderId);
return false;
}
String title = "有一笔" + addWord + "超时的订单!";
String content = "您有一笔" + addWord + "超时的订单[" + orderId + "],请及时处理。";
log.debug("[订单{}超时处理] 准备推送消息. 标题: {}, 内容: {}", addWord, title, content);
// 构造推送消息内容
JSONObject payload = new JSONObject();
if (willExpire) {
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ONLINE_ORDER_LIST);
} else {
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ABNORMAL_ORDER_LIST);
}
payload.put("orderId", orderId);
log.debug("[订单{}超时处理] 推送消息载荷已准备: {}", addWord, payload);
// 发送推送消息给商家员工
log.debug("[订单{}超时处理] 发送推送消息给商家员工. 店铺ID: {}, 订单号: {}", addWord, storeId, orderId);
pushMessageService.noticeMerchantEmployeeOrderAction(
Convert.toInt(storeId), orderId, title,
content, payload);
log.info("[订单{}超时处理] 订单超时消息处理完成. 订单号: {}, 店铺ID: {}, 推送类别: {}",
addWord, orderId, storeId, CommonConstant.PUSH_MSG_CATE_MCH_ABNORMAL_ORDER_LIST);
return true;
} catch (Exception e) {
log.error("[订单超时处理] 处理订单超时消息时发生异常. 订单号: {}, 店铺ID: {}", orderId, storeId, e);
return false;
}
}
}

View File

@ -36,10 +36,20 @@ public interface ShopOrderLklService extends IBaseService<ShopOrderLkl> {
* 根据拉卡拉的回调数据更新拉卡拉的订单信息
*
* @param lklPayNotifyDataJson 异步通知返回的body json数据
* // {"out_trade_no":"202203151637334864280014","trade_no":"2022031566210203291925","log_no":"66210203291925",
* // 非合单返回的数据异步通知返回的 body json数据{"out_trade_no":"202203151637334864280014","trade_no":"2022031566210203291925","log_no":"66210203291925",
* // "acc_trade_no":"2022031522001483661454130929 ","trade_status":"SUCCESS","trade_state":"SUCCESS","total_amount":"1",
* // "payer_amount":"1","acc_settle_amount":"1","trade_time":"20220315163808","user_id1":"app***@163.com",
* // "user_id2":"2088432881453660","notify_url":"https://www.baidu.com","account_type":"ALIPAY","card_type":"99"}
* <p>
* // 合单返回的数据异步通知返回的 body json数据{"out_trade_no":"DD-20250830-10","trade_no":"20250830110113130266250034160499","log_no":"66250034160499",
* // "acc_trade_no":"4200002826202508306761393882","trade_status":"SUCCESS","trade_state":"SUCCESS","total_amount":"2","payer_amount":"2","acc_settle_amount":"2",
* // "acc_mdiscount_amount":"0","acc_discount_amount":"0","trade_time":"20250830180435","user_id1":"oDVKR7T0qxg6O8tqIL9SgY6LXqqQ",
* // "user_id2":"oVxsc1QRAqDRv_gAmXuLZwSVSL18","notify_url":"https://mall.gpxscs.cn/mobile/pay/index/lkl_wxPay_notify_url","account_type":"WECHAT",
* // "bank_type":"OTHERS","card_type":"02","merchant_no":"8226330541100GU","remark":"","sub_mch_id":"803819329",
* // "out_split_rsp_infos":[{"sub_trade_no":"20250830110113130266250034112794","sub_log_no":"66250034112794","out_sub_trade_no":"ORD_DD-20250830-10",
* // "merchant_no":"8226330541100GU","term_no":"N5817779","amount":"1","settle_type":"0"},{"sub_trade_no":"20250830110113130266250034160498","sub_log_no":"66250034160498",
* // "out_sub_trade_no":"DF_DD-20250830-10","merchant_no":"822584059990FYP","term_no":"N5811590","amount":"1","settle_type":"0"}],"trade_req_date":"20250830","gb_amount":"",
* // "qb_amount":""}
* @return
*/
Boolean addOrUpdateByLklPayNotifyDataJson(JSONObject lklPayNotifyDataJson);
@ -53,4 +63,54 @@ public interface ShopOrderLklService extends IBaseService<ShopOrderLkl> {
* @return
*/
List<ShopOrderLkl> selectByOrderId(String orderId, String lklLogNo, String storeId);
/**
* 根据店铺Id订单编号查询一条记录
*
* @param storeId
* @param orderId
* @return
*/
ShopOrderLkl getByStoreIdAndOrderId(Integer storeId, String orderId);
/**
* 根据商户号交易流水号对账单流水号查询一条记录
*
* @param lklMerchantNo
* @param lklTradeNo
* @param lklSubLogNo
* @return
*/
ShopOrderLkl getByLklMchNoAndSubTradeNoAndSubLogNo(String lklMerchantNo, String lklTradeNo, String lklSubLogNo);
/**
* 根据商户号确认收货交易流水号确认收货对账单流水号查询一条记录
*
* @param lklMerchantNo 拉卡拉商户号
* @param lklReceiveTradeNo 拉卡拉确认收货交易流水号
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @return ShopOrderLkl 拉卡拉订单记录
*/
ShopOrderLkl getByLklMchNoAndReceiveTradeNoAndReceiveLogNo(String lklMerchantNo, String lklReceiveTradeNo, String lklReceiveLogNo);
/**
* 根据商户号确认收货交易流水号确认收货对账单流水号更新确认收货状态
*
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @param separateStatus 分账状态1-已分账2-未分账3-分账已失败
* @param separateRemark 分账问题备注
* @return
*/
Boolean updateSeparateStatusByReceiveLogNo(String lklReceiveLogNo, Integer separateStatus, String separateRemark);
/**
* 不抛异常更新
*
* @param record
* @return
*/
Boolean safeUpdate(ShopOrderLkl record);
}

View File

@ -175,6 +175,17 @@ public interface ShopOrderReturnService extends IBaseService<ShopOrderReturn> {
*/
CommonResult addWholeItems(String orderId, Boolean isSystemOpt, String remark);
/**
* 对已存在部分退款的订单进行剩余商品的全部退款
* 该方法用于处理订单中部分商品已经申请退款后对剩余商品进行整单退款的场景
*
* @param orderId 订单ID
* @param isSystemOpt 是否系统自动操作
* @param remark 退单备注
* @return CommonResult 退款申请结果
*/
CommonResult addRemainingItems(String orderId, Boolean isSystemOpt, String remark);
/**
* 退货单转单-供应商
*

View File

@ -331,6 +331,10 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Autowired
private ShopDistributionPlantformUserService shopDistributionPlantformUserService;
@Lazy
@Autowired
private ShopOrderLklService shopOrderLklService;
@Lazy
@Autowired
private MessageService messageService;
@ -585,7 +589,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
if (StrUtil.isNotBlank(order_key)) {
if (order_key.startsWith("DD-")) {
if (order_key.startsWith("DD_") || order_key.startsWith("DD-")) {
params.put("order_id:eq", order_key);
} else {
params.put("order_title:like", order_key);
@ -628,43 +632,61 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
/**
* 订单详情
* 获取订单详情
*
* @return
* @param order_id 订单ID
* @return 订单详情数据
*/
@Override
public Map detail(String order_id) {
log.debug("开始获取订单详情订单ID: {}", order_id);
Map data = new HashMap();
List<String> order_ids = Convert.toList(String.class, order_id);
if (CollUtil.isEmpty(order_ids)) {
log.warn("订单ID列表为空");
return data;
}
// 获取订单信息
Map row = Convert.toMap(String.class, Object.class, get(order_ids.get(0)));
if (CollUtil.isEmpty(row)) {
log.warn("订单信息不存在订单ID: {}", order_ids.get(0));
throw new ApiException(I18nUtil._("订单不存在!"));
}
//买家订单或者 // todo 分销订单
UserDto user = getCurrentUser();
if (user == null) {
log.warn("用户信息异常");
throw new ApiUserException(I18nUtil._("用户信息异常!"));
}
Integer user_id = user.getId();
log.debug("当前用户ID: {}", user_id);
QueryWrapper<ShopDistributionUserOrder> userOrderQueryWrapper = new QueryWrapper<>();
userOrderQueryWrapper.eq("user_id", user_id).in("order_id", order_ids);
ShopDistributionUserOrder distributionUserOrder = shopDistributionUserOrderService.findOne(userOrderQueryWrapper);
Map distributionUserOrderMap = Convert.toMap(String.class, Object.class, distributionUserOrder);
if (CheckUtil.checkDataRights(user_id, row, "buyer_user_id") || CheckUtil.checkDataRights(user_id, distributionUserOrderMap, "user_id")) {
data = getOrderDetail(order_id, row);
// 检查用户是否有权限访问该订单作为买家或分销商
boolean hasBuyerRights = CheckUtil.checkDataRights(user_id, row, "buyer_user_id");
boolean hasDistributionRights = CheckUtil.checkDataRights(user_id, distributionUserOrderMap, "user_id");
log.debug("订单访问权限检查,买家权限: {}, 分销权限: {}", hasBuyerRights, hasDistributionRights);
//diy, 判断是否上传素材
if (hasBuyerRights || hasDistributionRights) {
data = getOrderDetail(order_id, row);
log.debug("获取订单详细信息完成");
// diy, 判断是否上传素材
List<Map> items = (List<Map>) data.get("items");
for (Map item : items) {
String design_file_images = Convert.toStr(item.get("design_file_images"));
if (StrUtil.isNotBlank(design_file_images)) {
// row = apply_filters("product-cart-add", row);
if (CollUtil.isNotEmpty(items)) {
for (Map item : items) {
String design_file_images = Convert.toStr(item.get("design_file_images"));
if (StrUtil.isNotBlank(design_file_images)) {
// row = apply_filters("product-cart-add", row);
log.debug("订单项包含设计文件图片");
}
}
}
@ -675,15 +697,19 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
Integer delivery_istimer = Convert.toInt(data.get("delivery_istimer"));
String deliveryTime = getDeliveryTime(delivery_time, delivery_time_rang, delivery_time_h, delivery_time_i, delivery_istimer);
data.put("delivery_time_name", deliveryTime);
log.debug("配送时间计算完成: {}", deliveryTime);
// 判断活动信息
Integer activity_type_id = Convert.toInt(data.get("activity_type_id"));
if (ObjectUtil.equal(StateCode.ACTIVITY_TYPE_GROUPBOOKING, activity_type_id)) {
log.debug("处理拼团活动订单");
QueryWrapper<ShopActivityGroupbookingHistory> historyQueryWrapper = new QueryWrapper<>();
historyQueryWrapper.eq("user_id", user_id).in("order_id", order_ids);
ShopActivityGroupbookingHistory group_booking_row = groupbookingHistoryService.findOne(historyQueryWrapper);
data.put("gb_id", group_booking_row.getGb_id());
if (group_booking_row != null) {
data.put("gb_id", group_booking_row.getGb_id());
log.debug("获取拼团ID: {}", group_booking_row.getGb_id());
}
}
Map queryParams = new HashMap();
@ -693,6 +719,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
Map result = payService.findConsumeRecordHandleName(queryParams);
data.putAll(result);
log.debug("支付记录处理完成");
// 订单倒计时
boolean show_cancel_time = accountBaseConfigService.getConfig("show_cancel_time", false);
@ -709,6 +736,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
order_time = DateUtil.offsetSecond(order_time, (int) (order_autocancel_time * 60 * 60));
long remain_pay_time = (order_time.getTime() - new DateTime().getTime()) / 1000;
data.put("remain_pay_time", remain_pay_time); // 修复以分钟扫描清除订单导致的误差
log.debug("订单自动取消倒计时计算完成,剩余时间: {}秒", remain_pay_time);
}
//自动收货倒计时 order_autofinish_time
@ -719,10 +747,15 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
long remain_autofinish_time = (order_deal_time.getTime() - new DateTime().getTime()) / 1000;
data.put("remain_autofinish_time", remain_autofinish_time); // 修复以分钟扫描清除订单导致的误差
log.debug("订单自动收货倒计时计算完成,剩余时间: {}秒", remain_autofinish_time);
}
} else {
log.warn("用户无权限访问该订单用户ID: {}, 订单ID: {}", user_id, order_id);
throw new ApiException(I18nUtil._("无该订单访问权限!"));
}
log.info("订单详情获取完成订单ID: {}", order_id);
return data;
}
@ -755,6 +788,14 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
if (CollUtil.isNotEmpty(order_info)) base_row.putAll(order_info);
if (CollUtil.isNotEmpty(order_data)) base_row.putAll(order_data);
// update 2025-09-10 增加KLK支付信息
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByStoreIdAndOrderId(shopOrderInfo.getStore_id(), order_id);
if (shopOrderLkl != null) {
base_row.put("lkl_merchant_no", shopOrderLkl.getLkl_merchant_no());
base_row.put("lkl_trade_no", shopOrderLkl.getWx_transaction_id());
base_row.put("lkl_log_no", shopOrderLkl.getLkl_sub_log_no());
}
// 是否为虚拟商品
ShopOrderChainCode shopOrderChainCode = orderChainCodeService.get(order_id);
Map order_chain_code = Convert.toMap(String.class, Object.class, shopOrderChainCode);
@ -2334,10 +2375,10 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
queryWrapper.eq("order_state_id", order_state_id).in("order_is_shipped", Arrays.asList(StateCode.ORDER_SHIPPED_STATE_NO, StateCode.ORDER_SHIPPED_STATE_PART)).eq("order_is_received", 0);
break;
case StateCode.ORDER_STATE_SHIPPED: // 待收获确认
shopOrderInfo.setOrder_is_received(1);
shopOrderInfo.setOrder_is_received(CommonConstant.Enable);
shopOrderInfo.setOrder_settlement_time(time);
shopOrderInfo.setOrder_qs_time(time);
queryWrapper.eq("order_state_id", order_state_id).eq("order_is_received", 0);
queryWrapper.eq("order_state_id", order_state_id).eq("order_is_received", CommonConstant.Disable);
break;
default:
shopOrderInfo.setOrder_settlement_time(time);
@ -3246,6 +3287,8 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
Integer order_item_inventory_lock = order_item_row.getOrder_item_inventory_lock();
String item_src_id = order_item_row.getItem_src_id();
logger.debug("尝试执行订单商品item_src_id:{} 锁定库存:{}", item_src_id, order_item_inventory_lock);
if (ObjectUtil.equal(1002, order_item_inventory_lock) && CheckUtil.isNotEmpty(item_src_id)) {
Long item_id = order_item_row.getItem_id();
if (shopProductItemService.lockSkuStock(item_id, order_item_row.getOrder_item_quantity()) <= 0) {
@ -3253,9 +3296,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
throw new ApiException(String.format(I18nUtil._("更改: %s 冻结库存失败!"), item_id));
}
logger.debug("成功执行订单商品item_src_id:{} 锁定库存:{}", item_src_id, order_item_inventory_lock);
// RMK 第三方数据同步相关redis 给这个商品减去对应的库存
Map<String, Integer> stockDeltaMap = new HashMap<>();
stockDeltaMap.put(Convert.toStr(item_src_id), -order_item_quantity);
stockDeltaMap.put(item_src_id, -order_item_quantity);
syncThirdDataService.incrProductStockToRedis(stockDeltaMap);
}
}
@ -4805,7 +4850,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
throw new ApiException(ResultCode.FAILED);
}
// 权限判断
// 用户权限判断
UserDto user = getCurrentUser();
if (user == null) {
throw new ApiUserException(I18nUtil._("用户信息异常!"));
@ -4902,87 +4947,145 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
/**
* 订单确认收货
* 确认收货处理
*
* @param order_ids 订单id
* @param order_rows 订单数据
* @param order_ids 订单ID列表
* @param order_rows 订单数据列表
* @return 是否处理成功
*/
@Override
public boolean receive(List<String> order_ids, List<ShopOrderBase> order_rows) {
// 检测数据是否合法,过滤允许修改的数据
if (CollUtil.isEmpty(order_ids)) {
log.warn("[确认收货] 参数校验失败订单ID列表为空");
throw new ApiException(I18nUtil._("请选择需要确认收货的订单!"));
}
if (CollUtil.isEmpty(order_rows)) {
log.debug("[确认收货] 订单数据列表为空根据订单ID列表查询订单数据");
order_rows = gets(order_ids);
}
List<String> receive_id_row = new ArrayList<>();
// 检查订单数据是否存在
if (CollUtil.isEmpty(order_rows)) {
log.warn("[确认收货] 无法获取订单数据: orderIds={}", order_ids);
throw new ApiException(I18nUtil._("无法获取订单数据!"));
}
List<String> receive_id_row = new ArrayList<>();
List<String> not_eligible_orders = new ArrayList<>(); // 记录不符合条件的订单
// 遍历订单列表处理满足收货条件的订单
for (ShopOrderBase order_row : order_rows) {
// 是否允许确认收货
// 判断订单是否可以确认收货
if (ifReceive(order_row.getOrder_state_id())) {
receive_id_row.add(order_row.getOrder_id());
// 增加积分和经验
// todo 目前付款支付积分此处为收货后发放
Integer user_id = order_row.getBuyer_user_id();
String order_id = order_row.getOrder_id();
Integer store_id = order_row.getStore_id();
ShopOrderData order_data_row = shopOrderDataService.get(order_id);
BigDecimal order_points_add = order_data_row.getOrder_points_add();
BigDecimal order_points_add_all = order_points_add.add(order_data_row.getOrder_double_points_add());
if (CheckUtil.isNotEmpty(order_points_add_all)) {
String desc = String.format(I18nUtil._("购物获取积分 %s订单号 %s"), order_points_add_all, order_id);
if (!payService.points(user_id, order_points_add_all, PointsType.POINTS_TYPE_CONSUME, desc, store_id, null, order_id)) {
throw new ApiException(I18nUtil._("积分操作失败!"));
}
}
// todo 根据送花郎插件是否开启显示是否需要分钱给不同商户
/*
boolean hall_enable = accountBaseConfigService.getConfig("hall_enable", false);
if (hall_enable) {
BigDecimal order_commission_fee = order_data_row.getOrder_commission_fee();
sendMoneyForTransfer(order_row, order_commission_fee);
}
*/
// todo 目前付款成功发放佣金此处为收货后发放
// 分销功能
String fx_settle_type = accountBaseConfigService.getConfig("fx_settle_type", "receive");
if (StrUtil.equals(fx_settle_type, "receive")) {
// todo settleDistributionUserOrder
shopDistributionUserOrderService.settleDistributionUserOrder(order_id);
}
// 重要拉卡拉给平台和代理商分账
Pair<Boolean, String> retOrderSeparateRet = lakalaApiService.innerDoOrderSeparate(order_row.getOrder_id(), Convert.toStr(order_row.getStore_id()));
if (!retOrderSeparateRet.getFirst()) {
throw new ApiException(I18nUtil._("平台或代理商分账失败: " + retOrderSeparateRet.getSecond()));
}
// 统计总营业额
ShopStoreAnalytics analytics_row = shopStoreAnalyticsService.get(store_id);
BigDecimal order_payment_amount = order_row.getOrder_payment_amount();
analytics_row.setStore_trade_amount(NumberUtil.add(analytics_row.getStore_trade_amount(), order_payment_amount));
if (!shopStoreAnalyticsService.edit(analytics_row)) {
throw new ApiException(ResultCode.FAILED);
}
log.info("[确认收货] 处理订单: orderId={}, currentState={}", order_row.getOrder_id(), order_row.getOrder_state_id());
} else {
not_eligible_orders.add(order_row.getOrder_id());
log.warn("[确认收货] 订单状态不满足收货条件: orderId={}, currentState={}", order_row.getOrder_id(), order_row.getOrder_state_id());
}
}
// 检查是否有符合条件的订单
if (CollUtil.isEmpty(receive_id_row)) {
throw new ApiException(I18nUtil._("无符合确认收货条件的订单!"));
log.warn("[确认收货] 无符合确认收货条件的订单,总订单数={},不符合条件订单数={}", order_rows.size(), not_eligible_orders.size());
// 记录所有订单的详细状态信息
for (ShopOrderBase order_row : order_rows) {
log.warn("[确认收货] 订单状态详情: orderId={}, currentState={}", order_row.getOrder_id(), order_row.getOrder_state_id());
}
// 如果是通过顺丰通知触发的收货且订单已经是完成状态则直接返回成功
if (order_rows.size() == 1 && not_eligible_orders.size() == 1) {
ShopOrderBase order = order_rows.get(0);
// 如果订单已经是完成状态则认为收货成功
if (ObjectUtil.equal(order.getOrder_state_id(), StateCode.ORDER_STATE_FINISH)) {
log.info("[确认收货] 订单已经是完成状态,直接返回成功: orderId={}", order.getOrder_id());
return true;
}
}
// throw new ApiException(I18nUtil._("无符合确认收货条件的订单!"));
log.info("[确认收货] 无符合确认收货条件的订单!");
return false;
}
log.info("[确认收货] 符合条件的订单数量: eligibleCount={}, totalChecked={}", receive_id_row.size(), order_rows.size());
// 处理符合条件的订单
for (ShopOrderBase order_row : order_rows) {
// 只处理符合条件的订单
if (!receive_id_row.contains(order_row.getOrder_id())) {
continue;
}
// 增加积分和经验
// todo 目前付款支付积分此处为收货后发放
Integer user_id = order_row.getBuyer_user_id();
String order_id = order_row.getOrder_id();
Integer store_id = order_row.getStore_id();
ShopOrderData order_data_row = shopOrderDataService.get(order_id);
if (order_data_row == null) {
log.warn("[确认收货] 无法获取订单数据: orderId={}", order_id);
continue;
}
BigDecimal order_points_add = order_data_row.getOrder_points_add();
BigDecimal order_double_points_add = order_data_row.getOrder_double_points_add();
BigDecimal order_points_add_all = order_points_add != null ? order_points_add : BigDecimal.ZERO;
order_points_add_all = order_points_add_all.add(order_double_points_add != null ? order_double_points_add : BigDecimal.ZERO);
// 发放购物积分
if (CheckUtil.isNotEmpty(order_points_add_all) && order_points_add_all.compareTo(BigDecimal.ZERO) > 0) {
String desc = String.format(I18nUtil._("购物获取积分 %s订单号 %s"), order_points_add_all, order_id);
log.debug("[确认收货] 发放购物积分: userId={}, points={}, desc={}", user_id, order_points_add_all, desc);
if (!payService.points(user_id, order_points_add_all, PointsType.POINTS_TYPE_CONSUME, desc, store_id, null, order_id)) {
log.error("[确认收货] 积分操作失败: userId={}, points={}, orderId={}", user_id, order_points_add_all, order_id);
// throw new ApiException(I18nUtil._("积分操作失败!"));
}
}
// todo 根据送花郎插件是否开启显示是否需要分钱给不同商户
/*
boolean hall_enable = accountBaseConfigService.getConfig("hall_enable", false);
if (hall_enable) {
BigDecimal order_commission_fee = order_data_row.getOrder_commission_fee();
sendMoneyForTransfer(order_row, order_commission_fee);
}
*/
// todo 目前付款成功发放佣金此处为收货后发放
// 分销功能
String fx_settle_type = accountBaseConfigService.getConfig("fx_settle_type", "receive");
if (StrUtil.equals(fx_settle_type, "receive")) {
log.debug("[确认收货] 处理分销订单: orderId={}", order_id);
// todo settleDistributionUserOrder
shopDistributionUserOrderService.settleDistributionUserOrder(order_id);
}
// 统计总营业额
ShopStoreAnalytics analytics_row = shopStoreAnalyticsService.get(store_id);
if (analytics_row != null) {
BigDecimal order_payment_amount = order_row.getOrder_payment_amount();
log.debug("[确认收货] 更新店铺营业额: storeId={}, amount={}", store_id, order_payment_amount);
analytics_row.setStore_trade_amount(NumberUtil.add(analytics_row.getStore_trade_amount(), order_payment_amount != null ? order_payment_amount : BigDecimal.ZERO));
if (!shopStoreAnalyticsService.edit(analytics_row)) {
log.error("[确认收货] 更新店铺营业额失败: storeId={}", store_id);
// throw new ApiException(ResultCode.FAILED);
}
} else {
log.warn("[确认收货] 无法获取店铺统计信息: storeId={}", store_id);
}
}
// 修改订单状态, 随机去一个订单获取店铺编号
ShopOrderBase shopOrderBase = order_rows.get(0);
ShopOrderBase shopOrderBase = order_rows.stream()
.filter(order -> receive_id_row.contains(order.getOrder_id()))
.findFirst()
.orElse(order_rows.get(0));
Integer store_id = shopOrderBase.getStore_id();
log.debug("[确认收货] 修改订单状态: storeId={}", store_id);
editNextState(receive_id_row, store_id, StateCode.ORDER_STATE_SHIPPED, order_rows, 0);
// 如果是商家且启用供应商则商家看到供应商店铺商品 store_type = 2
@ -4990,7 +5093,9 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
UserDto user = getCurrentUser();
store_id = user != null ? Convert.toInt(user.getStore_id(), 0) : 0;
// 处理供应商市场的库存增加逻辑
if (ifSupplierMarket && CheckUtil.isNotEmpty(store_id)) {
log.debug("[确认收货] 处理供应商市场库存: storeId={}", store_id);
// 供应商商品增加商家库存
QueryWrapper<ShopOrderItem> itemQueryWrapper = new QueryWrapper<>();
itemQueryWrapper.in("order_id", receive_id_row);
@ -5002,30 +5107,39 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
productItemQueryWrapper.in("item_src_id", item_src_ids).eq("store_id", store_id);
List<ShopProductItem> product_item_rows = shopProductItemService.find(productItemQueryWrapper);
// 更新供应商商品库存
log.debug("[确认收货] 更新供应商商品库存: itemsCount={}", product_item_rows.size());
for (ShopProductItem product_item_row : product_item_rows) {
String item_src_id = product_item_row.getItem_src_id();
Optional<ShopOrderItem> orderItemOpl = order_item_rows.stream().filter(s -> ObjectUtil.equal(s.getItem_id(), item_src_id)).findFirst();
if (orderItemOpl.isPresent()) {
ShopOrderItem shopOrderItem = orderItemOpl.get();
Integer order_item_quantity = shopOrderItem.getOrder_item_quantity();
product_item_row.setItem_quantity(product_item_row.getItem_quantity() + order_item_quantity);
if (!shopProductItemService.edit(product_item_row)) {
throw new ApiException(ResultCode.FAILED);
if (order_item_quantity != null && order_item_quantity > 0) {
log.debug("[确认收货] 更新商品库存: itemId={}, oldQuantity={}, addQuantity={}",
item_src_id, product_item_row.getItem_quantity(), order_item_quantity);
product_item_row.setItem_quantity(product_item_row.getItem_quantity() + order_item_quantity);
if (!shopProductItemService.edit(product_item_row)) {
log.error("[确认收货] 更新商品库存失败: itemId={}", item_src_id);
// throw new ApiException(ResultCode.FAILED);
}
}
}
}
}
}
log.info("[确认收货] 处理完成: processedOrders={}", receive_id_row.size());
return true;
}
/**
* 订单确认收货定时任务用途
*
* @param order_id 订单id
*/
@Override
@Transactional
public boolean receive(String order_id, ShopOrderBase orderBase) {
return receive(Collections.singletonList(order_id), null);
}
@ -7847,12 +7961,13 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
/**
* 是否可以确认收货
*
* @param order_state_id 订单状态
* @return boolean true-false-不可
* @access public
* @param orderStateId 订单状态
* @return boolean true-可确认收货false-不可确认收货
*/
private boolean ifReceive(Integer order_state_id) {
return ObjectUtil.equal(order_state_id, StateCode.ORDER_STATE_SHIPPED);
private boolean ifReceive(Integer orderStateId) {
// 只有已发货已签收状态的订单才能确认收货
return ObjectUtil.equal(orderStateId, StateCode.ORDER_STATE_SHIPPED) ||
ObjectUtil.equal(orderStateId, StateCode.ORDER_STATE_RECEIVED);
}
/**
@ -8005,6 +8120,14 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
order_info_row.putAll(order_data_row);
order_info_row.put("item", order_item_temp_rows.get(order_id));
// update 2025-09-10 增加KLK支付信息
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByStoreIdAndOrderId(store_id, order_id);
if (shopOrderLkl != null) {
order_info_row.put("lkl_merchant_no", shopOrderLkl.getLkl_merchant_no());
order_info_row.put("lkl_trade_no", shopOrderLkl.getWx_transaction_id());
order_info_row.put("lkl_log_no", shopOrderLkl.getLkl_sub_log_no());
}
Optional<Map> store_row_Opl = store_rows.stream().filter(s -> ObjectUtil.equal(store_id, Convert.toInt(s.get("store_id")))).findFirst();
Map store_row = store_row_Opl.orElseGet(HashMap::new);
order_info_row.put("self_support", store_row.get("store_is_selfsupport"));
@ -8513,12 +8636,14 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
* 2商家的详细地址电话和经纬度
* 3收货人的姓名电话详细地址经纬度
*
* @param shopOrderId 商城订单id
* @return
* @param devId 开发者ID
* @param shopOrderId 商城订单id
* @param orderPickupNum 订单取货编号
* @return SFCreateOrderReq 顺丰同城订单请求对象如果构建失败返回null
*/
public SFCreateOrderReq buildSFOrderData(Integer devId, String shopOrderId, Long orderPickupNum) {
if (StrUtil.isBlank(shopOrderId)) {
logger.error("缺少订单Id");
logger.error("构建顺丰订单失败:缺少订单IddevId={}, shopOrderId={}", devId, shopOrderId);
return null;
}
@ -8528,13 +8653,13 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
queryWrapper.eq("order_id", shopOrderId);
List<ShopOrderItem> shopOrderItemList = shopOrderItemService.list(queryWrapper);
if (shopOrderBase == null || shopOrderData == null || CollUtil.isEmpty(shopOrderItemList)) {
logger.error("无法获取订单信息!");
logger.error("构建顺丰订单失败无法获取订单信息订单ID={}", shopOrderId);
return null;
}
Integer storeId = shopOrderBase.getStore_id();
if (storeId == null || storeId <= 0) {
logger.error("缺少店铺 Id");
logger.error("构建顺丰订单失败缺少店铺Id订单ID={}storeId={}", shopOrderId, storeId);
return null;
}
@ -8543,12 +8668,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
ShopOrderDeliveryAddress shopOrderDeliveryAddress = shopOrderDeliveryAddressService.selectByOrderId(shopOrderId);
ShopStoreSameCityTransportBase shopStoreSameCityTransportBase = shopStoreSameCityTransportBaseService.getShopStoreSameCityTransportBaseById(storeId.longValue());
if (shopStoreBase == null || shopStoreInfo == null || shopOrderDeliveryAddress == null || shopStoreSameCityTransportBase == null) {
logger.error("无法获取店铺信息!");
return null;
}
if (shopStoreSameCityTransportBase == null) {
logger.error("请配置同城配送设置!");
logger.error("构建顺丰订单失败无法获取店铺信息订单ID={}storeId={}", shopOrderId, storeId);
return null;
}
@ -8556,11 +8676,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
String shopId = "3243279847393";
//3269768224353 到时启用这个正式店铺 type:便利店
Integer businessType = 6;// 生鲜分类
if (enable_sf_express.equals(CommonConstant.Enable)) {//开启正式配送服务的时候
if (CommonConstant.Enable.equals(enable_sf_express)) {//开启正式配送服务的时候
// 顺丰同城业务员给的店铺id
shopId = shopStoreSameCityTransportBase.getShop_id();
if (StrUtil.isBlank(shopId)) {
logger.error("请联系顺丰同城配置店铺ID");
logger.error("构建顺丰订单失败请联系顺丰同城配置店铺ID订单ID={}storeId={}", shopOrderId, storeId);
return null;
}
@ -8582,8 +8702,8 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
sfCreateOrderReq.setVersion(19);
sfCreateOrderReq.setReturn_flag(511);
Integer productNum = 0;
Integer productTypeNum = shopOrderItemList.size();
Integer totalProductQuantity = 0;
Integer productTypeCount = shopOrderItemList.size();
// 订单详情信息
SFOrderDetailReq orderDetail = new SFOrderDetailReq();
@ -8592,24 +8712,35 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
List<SFOrderProductDetailReq> orderProductList = new ArrayList<>();
for (ShopOrderItem shopOrderItem : shopOrderItemList) {
productNum += shopOrderItem.getOrder_item_quantity();
totalProductQuantity += shopOrderItem.getOrder_item_quantity();
// 产品详情
SFOrderProductDetailReq orderProductDetail = new SFOrderProductDetailReq();
orderProductDetail.setProduct_id(shopOrderItem.getProduct_id());
orderProductDetail.setProduct_name(shopOrderItem.getItem_name());
orderProductDetail.setProduct_num(shopOrderItem.getOrder_item_quantity());
// 单个商品金额=数量*单价单位分
Integer itemPrice = shopOrderItem.getOrder_item_unit_price().multiply(BigDecimal.valueOf(shopOrderItem.getOrder_item_quantity())).multiply(BigDecimal.valueOf(100)).intValue();
// 单个商品金额=单价*数量单位分
BigDecimal itemPriceDecimal = shopOrderItem.getOrder_item_unit_price()
.multiply(BigDecimal.valueOf(100))
.multiply(BigDecimal.valueOf(shopOrderItem.getOrder_item_quantity()));
Integer itemPrice = itemPriceDecimal.intValue();
orderProductDetail.setProduct_price(itemPrice);
orderProductList.add(orderProductDetail);
// 记录商品详情日志便于部署后排查问题
logger.debug("订单{}商品详情商品ID={}, 商品名称={}, 数量={}, 单价(分)={}, 小计(分)={}",
shopOrderId,
shopOrderItem.getProduct_id(),
shopOrderItem.getItem_name(),
shopOrderItem.getOrder_item_quantity(),
shopOrderItem.getOrder_item_unit_price().multiply(BigDecimal.valueOf(100)).intValue(),
itemPrice);
}
orderDetail.setProduct_type(businessType); // 店铺主营商品分类参考https://commit-openic.sf-express.com/#/apidoc
orderDetail.setTotal_price(shopOrderBase.getOrder_payment_amount().multiply(BigDecimal.valueOf(100)).intValue()); // 单位分
orderDetail.setWeight_gram(0); // 重量一律传 0kg 谢总本地运营商协商好的
orderDetail.setProduct_num(productNum); //物品个数
orderDetail.setProduct_type_num(productTypeNum); //物品种类个数
orderDetail.setProduct_num(totalProductQuantity); //物品个数
orderDetail.setProduct_type_num(productTypeCount); //物品种类个数
// 订单里的所有商品列表
orderDetail.setProduct_detail(orderProductList);
@ -8623,6 +8754,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
// 店铺信息发货人信息
Pair<Boolean, StandardAddressDTO> pairShopAddr = shopStoreBaseService.checkStoreAddress(shopStoreBase);
if (!pairShopAddr.getFirst()) {
logger.error("构建顺丰订单失败店铺地址校验失败订单ID={}storeId={}", shopOrderId, storeId);
return null;
}
shop.setShop_name(shopStoreBase.getStore_name());
shop.setShop_phone(shopStoreInfo.getStore_tel());
shop.setShop_address(pairShopAddr.getSecond().getFullAddress());
@ -8631,6 +8767,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
// 收货人信息
Pair<Boolean, StandardAddressDTO> pairReceiveAddr = shopOrderDeliveryAddressService.checkAddress(shopOrderDeliveryAddress);
if (!pairReceiveAddr.getFirst()) {
logger.error("构建顺丰订单失败收货地址校验失败订单ID={}storeId={}", shopOrderId, storeId);
return null;
}
receive.setUser_name(shopOrderDeliveryAddress.getDa_name());
receive.setUser_phone(shopOrderDeliveryAddress.getDa_mobile());
receive.setUser_address(pairReceiveAddr.getSecond().getFullAddress());
@ -8655,6 +8796,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
sfCreateOrderReq.setShop(shop);
sfCreateOrderReq.setReceive(receive);
// 记录完整的订单信息便于部署后排查问题
logger.info("成功构建顺丰订单订单ID={}, 店铺ID={}, 顺丰同城店铺ID={}, 商品种类数={}, 商品总数={}, 总价(分)={}",
shopOrderId, storeId, shopId, productTypeCount, totalProductQuantity,
shopOrderBase.getOrder_payment_amount().multiply(BigDecimal.valueOf(100)).intValue());
return sfCreateOrderReq;
}
@ -9039,38 +9185,6 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
}
/**
* 预处理发货订单超时消息(发到 mq 触发超时事件发出推送消息)
*
* @param storeId 店铺ID
* @param orderId 订单ID
* @param expireSeconds 过期时间
* @return 是否发送成功
*/
// @Async
// @Override
// public Boolean preSendExpiredSFOrderPushMessage(Integer storeId, String orderId, Long expireSeconds) {
// try {
// // 构建延迟消息内容
// JSONObject jsonObject = new JSONObject();
// jsonObject.put("category", MqConstant.DEAD_EVENT_CATE_ORDER_EXPIRED); // 消息分类1-订单超时消息
// jsonObject.put("orderId", orderId); // 订单ID
// jsonObject.put("storeId", storeId); // 店铺ID
// jsonObject.put("title", "有一笔已超时的订单!"); // 消息标题
// jsonObject.put("message", "您有一笔已超时的订单[" + orderId + "],请及时处理。"); // 消息内容
//
// // 发送延迟消息
// mqMessageService.sendDelayMessage(jsonObject.toString(), expireSeconds * 1000); // 转换为毫秒
//
// return true;
// } catch (Exception e) {
// log.error("发送延迟订单超时消息失败店铺ID{}订单ID{},过期时间:{}秒",
// storeId, orderId, expireSeconds, e);
// return false;
// }
// }
/**
* 取货单号格式化
*
@ -9112,11 +9226,11 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
// 确保比例在合理范围 [0, 100]
storeSplitRatio = storeSplitRatio.max(BigDecimal.ZERO).min(new BigDecimal("100"));
storeSplitRatio = storeSplitRatio.max(BigDecimal.ZERO).min(new BigDecimal(100));
// 分账金额 = 支付金额 × 平台和代理商分账比例 ÷ 100 将百分比转换为小数
BigDecimal result = pendingAmount.multiply(new BigDecimal("100").subtract(storeSplitRatio))
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_DOWN); // 保留两位小数
BigDecimal result = pendingAmount.multiply(new BigDecimal(100).subtract(storeSplitRatio))
.divide(new BigDecimal(100), 2, RoundingMode.HALF_DOWN); // 保留两位小数
logger.debug("计算分账金额: storeId={}, pendingAmount={}, ratio={}, result={}",
storeId, pendingAmount, storeSplitRatio, result);

View File

@ -229,81 +229,127 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
/**
* 更改商家订单的状态出库状态发货状态并写入订单状态更改日志
*
* @param orderId
* @param orderStatus 约定0值不更改
* @param orderId 订单ID
* @param orderStatus 订单状态约定0值不更改
* @param orderIsOutStatus 出库状态约定0值不更改
* @param orderIsShippedStatus 发货状态约定0值不更改
* @return
* @return Boolean 是否更新成功
*/
@Transactional
@Override
public Boolean changeOrderStatus(String orderId, Integer orderStatus, Integer orderIsOutStatus, Integer orderIsShippedStatus) {
if (orderId == null || orderStatus == null) {
return false;
}
ShopOrderInfo shopOrderInfo = getById(orderId);
if (shopOrderInfo == null) {
return false;
}
Integer preOrderStatus = shopOrderInfo.getOrder_state_id();
int paramsCnt = 0;
UpdateWrapper<ShopOrderInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("order_id", orderId);
if (orderStatus > 0) {
updateWrapper.set("order_state_id", orderStatus);
paramsCnt++;
}
if (orderIsOutStatus > 0 && !shopOrderInfo.getOrder_is_out().equals(orderIsOutStatus)) {
updateWrapper.set("order_is_out", orderStatus);
paramsCnt++;
}
if (orderStatus > 0 && !shopOrderInfo.getOrder_is_shipped().equals(orderIsShippedStatus)) {
updateWrapper.set("order_is_shipped", orderIsShippedStatus);
paramsCnt++;
}
if (paramsCnt <= 0) {
// 无修改的状态
return false;
}
if (!update(updateWrapper)) {
return false;
}
UpdateWrapper<ShopOrderBase> updateShopOrderBaseWrapper = new UpdateWrapper<>();
updateShopOrderBaseWrapper.eq("order_id", orderId);
if (orderStatus > 0) {
updateShopOrderBaseWrapper.set("order_state_id", orderStatus);
if (!shopOrderBaseService.update(updateShopOrderBaseWrapper)) {
try {
// 参数校验
if (orderId == null || orderStatus == null) {
logger.warn("[订单状态变更] 参数校验失败: orderId或orderStatus为空, orderId={}, orderStatus={}", orderId, orderStatus);
return false;
}
}
// 远程调用 更改交易订单状态
if (!payService.changePayConsumeTradeState(orderId, orderStatus, 0)) {
logger.error("远程调用更改交易订单状态失败!");
// 获取订单信息
ShopOrderInfo shopOrderInfo = getById(orderId);
if (shopOrderInfo == null) {
logger.warn("[订单状态变更] 未找到订单信息: orderId={}", orderId);
return false;
}
logger.debug("[订单状态变更] 开始处理订单状态变更: orderId={}, currentStatus={}, newStatus={}, outStatus={}, shippedStatus={}",
orderId, shopOrderInfo.getOrder_state_id(), orderStatus, orderIsOutStatus, orderIsShippedStatus);
Integer preOrderStatus = shopOrderInfo.getOrder_state_id();
int paramsCnt = 0;
UpdateWrapper<ShopOrderInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("order_id", orderId);
// 更新订单状态
if (orderStatus > 0) {
updateWrapper.set("order_state_id", orderStatus);
// 已签收或已完成更改两个时间
if (StateCode.ORDER_STATE_RECEIVED == orderStatus.intValue() || orderStatus == StateCode.ORDER_STATE_FINISH) {
updateWrapper.set("order_deal_time", System.currentTimeMillis());
updateWrapper.set("order_qs_time", new Date());
}
paramsCnt++;
logger.debug("[订单状态变更] 添加订单状态更新条件: orderId={}, status={}", orderId, orderStatus);
}
// 更新出库状态
if (orderIsOutStatus != null && orderIsOutStatus > 0 &&
!ObjectUtil.equal(shopOrderInfo.getOrder_is_out(), orderIsOutStatus)) {
updateWrapper.set("order_is_out", orderIsOutStatus);
paramsCnt++;
logger.debug("[订单状态变更] 添加出库状态更新条件: orderId={}, outStatus={}", orderId, orderIsOutStatus);
}
// 更新发货状态
if (orderIsShippedStatus != null && orderIsShippedStatus > 0 &&
!ObjectUtil.equal(shopOrderInfo.getOrder_is_shipped(), orderIsShippedStatus)) {
updateWrapper.set("order_is_shipped", orderIsShippedStatus);
paramsCnt++;
logger.debug("[订单状态变更] 添加发货状态更新条件: orderId={}, shippedStatus={}", orderId, orderIsShippedStatus);
}
// 检查是否有需要更新的字段
if (paramsCnt <= 0) {
logger.info("[订单状态变更] 无需要更新的字段: orderId={}", orderId);
// 无修改的状态
return false;
}
// 执行订单信息更新
logger.debug("[订单状态变更] 执行订单信息更新: orderId={}, updateFields={}", orderId, paramsCnt);
if (!update(updateWrapper)) {
logger.error("[订单状态变更] 订单信息更新失败: orderId={}", orderId);
return false;
}
// 更新订单基础信息中的状态
UpdateWrapper<ShopOrderBase> updateShopOrderBaseWrapper = new UpdateWrapper<>();
updateShopOrderBaseWrapper.eq("order_id", orderId);
if (orderStatus > 0) {
updateShopOrderBaseWrapper.set("order_state_id", orderStatus);
logger.debug("[订单状态变更] 执行订单基础信息更新: orderId={}, status={}", orderId, orderStatus);
if (!shopOrderBaseService.update(updateShopOrderBaseWrapper)) {
logger.error("[订单状态变更] 订单基础信息更新失败: orderId={}", orderId);
return false;
}
}
// 写入订单状态更改日志
logger.debug("[订单状态变更] 写入订单状态变更日志: orderId={}, preStatus={}, newStatus={}", orderId, preOrderStatus, orderStatus);
ShopOrderStateLog shopOrderStateLog = new ShopOrderStateLog();
shopOrderStateLog.setOrder_id(orderId);
shopOrderStateLog.setOrder_state_is_sync(0);
shopOrderStateLog.setOrder_state_id(orderStatus);
shopOrderStateLog.setOrder_state_time(new Date());
shopOrderStateLog.setOrder_state_pre_id(preOrderStatus);
shopOrderStateLog.setOrder_state_type(shopBaseStateCodeService.getText(orderStatus, null));
shopOrderStateLog.setOrder_state_note(CommonUtil.getOrderStateNote(preOrderStatus, orderStatus));
shopOrderStateLog.setUser_id(0);
shopOrderStateLog.setUser_nickname("SfExpress");
boolean logResult = shopOrderStateLogService.add(shopOrderStateLog);
if (logResult) {
logger.info("[订单状态变更] 订单状态变更成功: orderId={}, preStatus={}, newStatus={}", orderId, preOrderStatus, orderStatus);
// 远程调用 更改交易订单状态
logger.debug("[订单状态变更] 调用支付服务更新交易订单状态: orderId={}, status={}", orderId, orderStatus);
if (!payService.changePayConsumeTradeState(orderId, orderStatus, 0)) {
logger.error("[订单状态变更] 远程调用更改交易订单状态失败: orderId={}", orderId);
return false;
}
} else {
logger.error("[订单状态变更] 订单状态日志写入失败: orderId={}", orderId);
}
return logResult;
} catch (Exception e) {
logger.error("[订单状态变更] 处理过程中发生异常: orderId={}, orderStatus={}, outStatus={}, shippedStatus={}",
orderId, orderStatus, orderIsOutStatus, orderIsShippedStatus, e);
return false;
}
// 写入订单状态更改日志
ShopOrderStateLog shopOrderStateLog = new ShopOrderStateLog();
shopOrderStateLog.setOrder_id(orderId);
shopOrderStateLog.setOrder_state_is_sync(0);
shopOrderStateLog.setOrder_state_id(orderStatus);
shopOrderStateLog.setOrder_state_time(new Date());
shopOrderStateLog.setOrder_state_pre_id(preOrderStatus);
shopOrderStateLog.setOrder_state_type(shopBaseStateCodeService.getText(orderStatus, null));
shopOrderStateLog.setOrder_state_note(CommonUtil.getOrderStateNote(preOrderStatus, orderStatus));
shopOrderStateLog.setUser_id(0);
shopOrderStateLog.setUser_nickname("SfExpress");
return shopOrderStateLogService.add(shopOrderStateLog);
}
/**

View File

@ -14,8 +14,10 @@ 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.suisung.mall.common.modules.order.ShopOrderBase;
import com.suisung.mall.common.modules.order.ShopOrderLkl;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.common.utils.JsonUtil;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
@ -50,7 +52,6 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
@Resource
private ShopOrderDataService shopOrderDataService;
@Override
public Boolean addOrUpdateByStoreOrder(ShopOrderLkl record) {
if (record == null
@ -122,97 +123,157 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
* "merchantNo": "8226330599900LN"
* }
* }
* @return
* @return Boolean 是否保存或更新成功
*/
@Override
public Boolean addOrUpdateByLklPayDataJson(JSONObject lklPayReqAndRespJson) {
if (lklPayReqAndRespJson == null) {
log.warn("参数为空,无法保存拉卡拉订单记录");
log.warn("[拉卡拉订单保存] 参数为空,无法保存拉卡拉订单记录");
return false;
}
log.debug("拉卡拉支付请求和响应数据 body:{}", lklPayReqAndRespJson);
log.debug("[拉卡拉订单保存] 开始处理拉卡拉支付请求和响应数据: {}", lklPayReqAndRespJson);
try {
// 获取请求体和响应体
JSONObject reqDataJson = lklPayReqAndRespJson.getJSONObject("req");// 驼峰命名
JSONObject reqDataJson = lklPayReqAndRespJson.getJSONObject("req");// 下划线命名
JSONObject respDataJson = JSONUtil.parseObj(lklPayReqAndRespJson.getByPath("resp.resp_data"));// 下划线命名
if (reqDataJson == null || respDataJson == null) {
log.error("请求或响应数据中的 req_data / resp_data 为空");
log.error("[拉卡拉订单保存] 请求或响应数据中的 req_data / resp_data 为空");
return false;
}
log.debug("[拉卡拉订单保存] 解析请求和响应数据完成");
// 提取订单号并校验
String orderId = JsonUtil.getJsonValueSmart(respDataJson, "out_trade_no");
if (StringUtils.isBlank(orderId)) {
log.error("订单ID为空无法保存拉卡拉支付记录");
log.error("[拉卡拉订单保存] 订单ID为空无法保存拉卡拉支付记录");
return false;
}
log.debug("[拉卡拉订单保存] 解析订单ID: {}", orderId);
ShopOrderBase shopOrderBase = shopOrderBaseService.get(orderId);
if (shopOrderBase == null) {
log.error("订单不存在,无法保存拉卡拉支付记录");
log.error("[拉卡拉订单保存] 订单不存在,无法保存拉卡拉支付记录: orderId={}", orderId);
return false;
}
log.debug("[拉卡拉订单保存] 获取订单基础信息完成: storeId={}", shopOrderBase.getStore_id());
ShopOrderLkl record = new ShopOrderLkl();
record.setOrder_id(orderId);
String outSeparateNo = JsonUtil.getJsonValueSmart(reqDataJson, "outTradeNo");
// 平台最低内部配送费
Integer shoppingFeeInner = Convert.toInt(JsonUtil.getJsonValueSmart(reqDataJson, "shopping_fee_inner"));
if (shoppingFeeInner == null) {
shoppingFeeInner = 0;
}
record.setOut_separate_no(outSeparateNo);
log.debug("[拉卡拉订单保存] 设置订单ID和分离订单号: orderId={} outSeparateNo={}", orderId, outSeparateNo);
// 设置请求内容
// 订单金额安全处理
Integer amount = JsonUtil.getJsonValueSmart(reqDataJson, "totalAmount", Integer.class);
if (amount == null) {
log.error("订单{}金额无效: {}", orderId, amount);
log.error("[拉卡拉订单保存] 订单{}金额无效: amount={}", orderId, amount);
return false;
}
record.setTotal_amt(amount); // 应支付总金额
record.setAccount_type(JsonUtil.getJsonValueSmart(reqDataJson, "accountType"));
record.setTrans_type(JsonUtil.getJsonValueSmart(reqDataJson, "transType"));
record.setNotify_url(JsonUtil.getJsonValueSmart(reqDataJson, "notifyUrl"));
record.setLkl_merchant_no(JsonUtil.getJsonValueSmart(reqDataJson, "merchantNo"));
record.setLkl_term_no(JsonUtil.getJsonValueSmart(reqDataJson, "termNo"));
record.setLkl_req(JSONUtil.toJsonStr(reqDataJson));
// 设置响应内容
record.setLkl_log_no(JsonUtil.getJsonValueSmart(respDataJson, "log_no"));
record.setLkl_trade_no(JsonUtil.getJsonValueSmart(respDataJson, "trade_no"));
record.setLkl_resp(JSONUtil.toJsonStr(respDataJson));
log.debug("[拉卡拉订单保存] 设置订单金额: amount={}分", amount);
// 关键数据获取店铺ID分账比例用到
Integer storeId = shopOrderBase.getStore_id();
record.setStore_id(Convert.toStr(storeId));
//平台内部配送费
record.setShopping_fee_inner(shoppingFeeInner);
log.debug("[拉卡拉订单保存] 设置店铺ID: storeId={}", storeId);
// 运费和商家分账比例
BigDecimal shipperFee = shopOrderDataService.getOrderShippingFee(orderId);
record.setShopping_fee(shipperFee.multiply(BigDecimal.valueOf(100)).intValue()); // 运费单位
record.setSplit_ratio(shopStoreBaseService.getStoreSplitRatio(storeId, false)); // 商家分账比例
BigDecimal shipperFee = shopOrderDataService.getOrderShippingFee(orderId); // 运费获取
if (shipperFee != null) {
int shipperFeeInCents = shipperFee.multiply(BigDecimal.valueOf(100)).intValue(); // 运费单位
record.setShopping_fee(shipperFeeInCents);
log.debug("[拉卡拉订单保存] 商家设置运费: 元={} 分={}", shipperFee, shipperFeeInCents);
} else {
log.debug("[拉卡拉订单保存] 未获取到运费信息");
}
return addOrUpdateByStoreOrder(record);
BigDecimal splitRatio = shopStoreBaseService.getStoreSplitRatio(storeId, false); // 商家分账比例计算
record.setSplit_ratio(splitRatio);
log.debug("[拉卡拉订单保存] 设置分账比例: ratio={}", splitRatio);
// 设置其他请求字段
String accountType = JsonUtil.getJsonValueSmart(reqDataJson, "accountType");
String transType = JsonUtil.getJsonValueSmart(reqDataJson, "transType");
String notifyUrl = JsonUtil.getJsonValueSmart(reqDataJson, "notifyUrl");
String receiveNotifyUrl = JsonUtil.getJsonValueSmart(reqDataJson, "complete_notify_url");
String merchantNo = JsonUtil.getJsonValueSmart(reqDataJson, "merchantNo");
String termNo = JsonUtil.getJsonValueSmart(reqDataJson, "termNo");
record.setAccount_type(accountType);
record.setTrans_type(transType);
record.setNotify_url(notifyUrl);
record.setReceive_notify_url(receiveNotifyUrl);
record.setLkl_merchant_no(merchantNo);
record.setLkl_term_no(termNo);
log.debug("[拉卡拉订单保存] 设置请求字段: accountType={} transType={} merchantNo={} termNo={}",
accountType, transType, merchantNo, termNo);
String reqJsonStr = JSONUtil.toJsonStr(reqDataJson);
record.setLkl_req(reqJsonStr);
log.debug("[拉卡拉订单保存] 设置请求JSON长度={}", reqJsonStr != null ? reqJsonStr.length() : 0);
// 设置响应内容
String logNo = JsonUtil.getJsonValueSmart(respDataJson, "log_no");
record.setLkl_log_no(logNo);
log.debug("[拉卡拉订单保存] 设置流水号: logNo={}", logNo);
String tradeNo = JsonUtil.getJsonValueSmart(respDataJson, "trade_no");
record.setLkl_trade_no(tradeNo);
log.debug("[拉卡拉订单保存] 设置交易号: tradeNo={}", tradeNo);
String respJsonStr = JSONUtil.toJsonStr(respDataJson);
record.setLkl_resp(respJsonStr);
log.debug("[拉卡拉订单保存] 设置响应JSON长度={}", respJsonStr != null ? respJsonStr.length() : 0);
log.debug("[拉卡拉订单保存] 调用addOrUpdateByStoreOrder方法保存记录: orderId={}", orderId);
Boolean result = addOrUpdateByStoreOrder(record);
if (result) {
log.info("[拉卡拉订单保存] 拉卡拉订单记录保存成功: orderId={}", orderId);
} else {
log.error("[拉卡拉订单保存] 拉卡拉订单记录保存失败: orderId={}", orderId);
}
return result;
} catch (Exception e) {
log.error("新增拉卡拉支付记录出错", e);
log.error("[拉卡拉订单保存] 新增拉卡拉支付记录出错", e);
return false;
}
}
/**
* 根据拉卡拉支付异步回调通知数据保存或更新拉卡拉订单记录
* 该方法用于处理拉卡拉支付平台发送的支付通知将通知中的数据保存到shop_order_lkl表中
*
* @param lklPayNotifyDataJson 拉卡拉支付通知的JSON数据
* @return Boolean 是否保存或更新成功
*/
@Override
public Boolean addOrUpdateByLklPayNotifyDataJson(JSONObject lklPayNotifyDataJson) {
if (lklPayNotifyDataJson == null) {
log.warn("参数为空,无法保存拉卡拉支付通知记录");
log.warn("[拉卡拉订单更新] 参数为空,无法保存拉卡拉支付通知记录");
return false;
}
log.debug("拉卡拉支付通知回调 body:{}", lklPayNotifyDataJson);
log.debug("[拉卡拉订单更新] 开始处理拉卡拉支付通知回调: {}", lklPayNotifyDataJson);
try {
// 获取订单ID
String orderId = lklPayNotifyDataJson.getStr("out_trade_no");
if (StringUtils.isBlank(orderId)) {
log.warn("订单ID为空无法保存拉卡拉支付通知记录");
log.warn("[拉卡拉订单更新] 订单ID为空无法保存拉卡拉支付通知记录");
return false;
}
log.debug("[拉卡拉订单更新] 解析订单ID: {}", orderId);
ShopOrderLkl record = new ShopOrderLkl();
record.setOrder_id(orderId);
@ -220,24 +281,69 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
// 设置必填字段并校验
String logNo = lklPayNotifyDataJson.getStr("log_no");
if (StringUtils.isBlank(logNo)) {
log.warn("log_no 为空,无法保存拉卡拉支付通知记录");
log.warn("[拉卡拉订单更新] log_no为空无法保存拉卡拉支付通知记录: orderId={}", orderId);
return false;
}
record.setLkl_log_no(logNo);
log.debug("[拉卡拉订单更新] 设置流水号: {}", logNo);
// 设置日期字段
record.setLkl_log_date(DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyy-MM-dd"));
String logDate = DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMdd");
record.setLkl_log_date(logDate);
log.debug("[拉卡拉订单更新] 设置日期字段: {}", logDate);
// 设置可选字段
record.setLkl_trade_no(lklPayNotifyDataJson.getStr("trade_no"));
record.setTrade_status(lklPayNotifyDataJson.getStr("trade_status"));
String tradeNo = lklPayNotifyDataJson.getStr("trade_no");
String tradeStatus = lklPayNotifyDataJson.getStr("trade_status");
String accTradeNo = lklPayNotifyDataJson.getStr("acc_trade_no");
record.setLkl_trade_no(tradeNo);
record.setTrade_status(tradeStatus);
record.setWx_transaction_id(accTradeNo); //账户端交易订单号 对应微信的用户交易单号
log.debug("[拉卡拉订单更新] 设置可选字段: tradeNo={} accTradeNo={} tradeStatus={}", tradeNo, accTradeNo, tradeStatus);
// 新增的订单字段,lkl_sub_log_no,out_separate_no,split_amt 四个字段无值就给主单的值
String outSeparateNo = JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "out_separate_no");
if (CheckUtil.isEmpty(outSeparateNo)) {
outSeparateNo = orderId;
}
record.setOut_separate_no(outSeparateNo);
String lklSubTradeNo = JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "lkl_sub_trade_no");
if (CheckUtil.isEmpty(lklSubTradeNo)) {
lklSubTradeNo = tradeNo;
}
record.setLkl_sub_trade_no(lklSubTradeNo);
String lklSubLogNo = JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "lkl_sub_log_no");
if (CheckUtil.isEmpty(lklSubLogNo)) {
lklSubLogNo = logNo;
}
record.setLkl_sub_log_no(lklSubLogNo);
Integer splitAmt = JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "split_amt", Integer.class);
if (CheckUtil.isEmpty(splitAmt)) {
splitAmt = record.getTotal_amt();
}
record.setSplit_amt(splitAmt);
log.debug("[拉卡拉订单更新] 设置订单字段: outSeparateNo={} lklSubTradeNo={} lklSubLogNo={} splitAmt={}",
outSeparateNo, lklSubTradeNo, lklSubLogNo, splitAmt);
// 安全地设置响应内容
record.setLkl_notify_resp(JSONUtil.toJsonStr(lklPayNotifyDataJson));
String notifyResp = JSONUtil.toJsonStr(lklPayNotifyDataJson);
record.setLkl_notify_resp(notifyResp);
log.debug("[拉卡拉订单更新] 设置通知响应内容,长度={}", notifyResp != null ? notifyResp.length() : 0);
return addOrUpdateByStoreOrder(record);
log.debug("[拉卡拉订单更新] 调用addOrUpdateByStoreOrder方法保存记录: orderId={}", orderId);
Boolean result = addOrUpdateByStoreOrder(record);
if (result) {
log.info("[拉卡拉订单更新] 拉卡拉订单记录保存成功: orderId={}", orderId);
} else {
log.error("[拉卡拉订单更新] 拉卡拉订单记录保存失败: orderId={}", orderId);
}
return result;
} catch (Exception e) {
log.error("修改拉卡拉支付通知记录出错", e);
log.error("[拉卡拉订单更新] 修改拉卡拉支付通知记录出错", e);
return false;
}
}
@ -273,4 +379,221 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
return CollectionUtil.newArrayList();
}
}
/**
* 根据店铺Id订单编号查询一条记录
*
* @param storeId
* @param orderId
* @return
*/
@Override
public ShopOrderLkl getByStoreIdAndOrderId(Integer storeId, String orderId) {
if (CheckUtil.isEmpty(storeId) || StringUtils.isBlank(orderId)) {
return null;
}
try {
QueryWrapper<ShopOrderLkl> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_id", storeId).eq("order_id", orderId).orderByAsc("id");
if (CheckUtil.isEmpty(storeId)) {
queryWrapper.eq("store_id", storeId);
}
return findOne(queryWrapper);
} catch (Exception e) {
log.error("getByStoreIdAndOrderId 查询异常, orderId: {}, storeId: {}", orderId, storeId, e);
return null;
}
}
/**
* 根据商户号子单交易流水号子单对账单流水号查询一条记录
*
* @param lklMerchantNo 拉卡拉商户号
* @param lklSubTradeNo 原拉卡拉交易流水号
* @param lklSubLogNo 原拉卡拉对账单流水号
* @return ShopOrderLkl 拉卡拉订单记录
*/
@Override
public ShopOrderLkl getByLklMchNoAndSubTradeNoAndSubLogNo(String lklMerchantNo, String lklSubTradeNo, String lklSubLogNo) {
// 检查参数是否全部为空
if (StringUtils.isAllBlank(lklMerchantNo, lklSubTradeNo, lklSubLogNo)) {
log.warn("[拉卡拉订单查询] 参数校验失败:所有查询条件均为空");
return null;
}
try {
log.debug("[拉卡拉订单查询] 开始查询, 商户号={}, 子订单交易号={}, 子订单对账流水号={}",
lklMerchantNo, lklSubTradeNo, lklSubLogNo);
QueryWrapper<ShopOrderLkl> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("id");
// 根据非空参数构建查询条件
if (StrUtil.isNotBlank(lklMerchantNo)) {
queryWrapper.eq("lkl_merchant_no", lklMerchantNo);
log.debug("[拉卡拉订单查询] 添加商户号查询条件: {}", lklMerchantNo);
}
if (StrUtil.isNotBlank(lklSubTradeNo)) {
queryWrapper.eq("lkl_sub_trade_no", lklSubTradeNo);
log.debug("[拉卡拉订单查询] 添加子订单交易号查询条件: {}", lklSubTradeNo);
}
if (StrUtil.isNotBlank(lklSubLogNo)) {
queryWrapper.eq("lkl_sub_log_no", lklSubLogNo);
log.debug("[拉卡拉订单查询] 添加子订单对账流水号查询条件: {}", lklSubLogNo);
}
ShopOrderLkl result = findOne(queryWrapper);
log.debug("[拉卡拉订单查询] 查询完成, 商户号={}, 子订单交易号={}, 子订单对账流水号={}, 查询结果={}",
lklMerchantNo, lklSubTradeNo, lklSubLogNo, result != null);
return result;
} catch (Exception e) {
log.error("[拉卡拉订单查询] 系统异常, 子订单商户号={}, 交易号={}, 子订单对账流水号={}",
lklMerchantNo, lklSubTradeNo, lklSubLogNo, e);
return null;
}
}
/**
* 根据商户号交易流水号对账单流水号查询一条记录
*
* @param lklMerchantNo 拉卡拉商户号
* @param lklReceiveTradeNo 拉卡拉确认收货交易流水号
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @return ShopOrderLkl 拉卡拉订单记录
*/
@Override
public ShopOrderLkl getByLklMchNoAndReceiveTradeNoAndReceiveLogNo(String lklMerchantNo, String lklReceiveTradeNo, String lklReceiveLogNo) {
// 检查参数是否全部为空
if (StringUtils.isAllBlank(lklMerchantNo, lklReceiveTradeNo, lklReceiveLogNo)) {
log.warn("[拉卡拉订单查询] 参数校验失败:所有查询条件均为空");
return null;
}
try {
log.debug("[拉卡拉订单查询] 开始查询, 商户号={}, 确认收货交易号={}, 确认收货对账流水号={}",
lklMerchantNo, lklReceiveTradeNo, lklReceiveLogNo);
QueryWrapper<ShopOrderLkl> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("id");
// 根据非空参数构建查询条件
if (StrUtil.isNotBlank(lklMerchantNo)) {
queryWrapper.eq("lkl_merchant_no", lklMerchantNo);
log.debug("[拉卡拉订单查询] 添加商户号查询条件: lklMerchantNo={}", lklMerchantNo);
}
if (StrUtil.isNotBlank(lklReceiveTradeNo)) {
queryWrapper.eq("lkl_receive_trade_no", lklReceiveTradeNo);
log.debug("[拉卡拉订单查询] 添加交易号查询条件: lklReceiveTradeNo={}", lklReceiveTradeNo);
}
if (StrUtil.isNotBlank(lklReceiveLogNo)) {
queryWrapper.eq("lkl_receive_log_no", lklReceiveLogNo);
log.debug("[拉卡拉订单查询] 添加收货流水号查询条件: lklReceiveLogNo={}", lklReceiveLogNo);
}
ShopOrderLkl result = findOne(queryWrapper);
log.debug("[拉卡拉订单查询] 查询完成, 商户号={}, 交易号={}, 流水号={}, 查询结果={}",
lklMerchantNo, lklReceiveTradeNo, lklReceiveLogNo, result != null);
return result;
} catch (Exception e) {
log.error("[拉卡拉订单查询] 系统异常, 商户号={}, 交易号={}, 流水号={}",
lklMerchantNo, lklReceiveTradeNo, lklReceiveLogNo, e);
return null;
}
}
/**
* 根据确认收货对账单流水号更新分账状态
*
* @param lklReceiveLogNo 拉卡拉确认收货对账单流水号
* @param separateStatus 分账状态1-已分账2-未分账3-分账已失败
* @param separateRemark 分账问题备注
* @return 更新结果 true-成功 false-失败
*/
@Override
public Boolean updateSeparateStatusByReceiveLogNo(String lklReceiveLogNo, Integer separateStatus, String separateRemark) {
// 检查参数是否全部为空
if (StringUtils.isBlank(lklReceiveLogNo) || separateStatus == null) {
log.warn("[更新分账状态] 参数校验失败:缺少必要参数, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
return false;
}
try {
log.info("[更新分账状态] 开始更新分账状态, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
UpdateWrapper<ShopOrderLkl> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("lkl_receive_log_no", lklReceiveLogNo);
updateWrapper.set("separate_status", separateStatus);
if (StrUtil.isNotBlank(separateRemark)) {
updateWrapper.set("separate_remark", separateRemark);
}
boolean result = update(updateWrapper);
if (result) {
log.info("[更新分账状态] 分账状态更新成功, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
} else {
log.warn("[更新分账状态] 分账状态更新失败,未找到匹配记录, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus);
}
return result;
} catch (Exception e) {
log.error("[更新分账状态] 分账状态更新异常, lklReceiveLogNo={}, separateStatus={}", lklReceiveLogNo, separateStatus, e);
return false;
}
}
/**
* 安全更新拉卡拉订单记录
* <p>
* 该方法用于更新拉卡拉订单记录不会抛出异常而是在出现错误时返回false
* </p>
*
* @param record 拉卡拉订单记录
* @return 更新结果 true-成功 false-失败
*/
@Override
public Boolean safeUpdate(ShopOrderLkl record) {
// 参数校验
if (record == null) {
log.warn("[安全更新] 参数校验失败:记录不能为空");
return false;
}
if (record.getId() == null) {
log.warn("[安全更新] 参数校验失败记录ID不能为空");
return false;
}
try {
log.debug("[安全更新] 开始更新拉卡拉订单记录, orderId={}, id={}",
record.getOrder_id(), record.getId());
boolean result = updateById(record);
if (result) {
log.info("[安全更新] 拉卡拉订单记录更新成功, orderId={}, id={}",
record.getOrder_id(), record.getId());
} else {
log.warn("[安全更新] 拉卡拉订单记录更新失败, orderId={}, id={}",
record.getOrder_id(), record.getId());
}
return result;
} catch (Exception e) {
log.error("[安全更新] 拉卡拉订单记录更新异常, orderId={}, id={}",
record.getOrder_id(), record.getId(), e);
return false;
}
}
}

View File

@ -182,73 +182,91 @@ public class OssServiceImpl implements OssService {
String ossUrl = null;
Integer uploadType = configService.getConfig("upload", 1);
if (uploadType.equals(1)) {
ossUrl = ConfigConstant.URL_BASE + "/admin/oss/upload/" + dir + "/" + uploadName; // 文件本地路径
} else if (uploadType.equals(2)) {
// oss 服务
try {
ossUrl = uploadObject2OSS(new File(uploadPath), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat("/").concat(uploadName), null, null, null);
} catch (Exception e) {
e.printStackTrace();
logger.error("文件上传失败!", e);
return null;
}
} else if (uploadType.equals(4)) {
ossUrl = uploadObject4OSS(new File(uploadPath), TENGXUN_DEFAULT_DIR.concat("/").concat(dir).concat("/").concat(uploadName));
}
String media_duration = "";
try {
media_duration = VideoUtil.getFormatDuration(uploadPath);
} catch (IOException e) {
throw new ApiException(String.format(I18nUtil._("解析音视频时长异常url【%s】"), uploadPath));
}
//截图
//todo 放入upload中
String thumb_file_url = "";
if (VideoUtil.videoAllowFiles.contains(VideoUtil.getVideoFormat(uploadPath))) {
// String cover_path = uploadPath.replace("." + VideoUtil.getVideoFormat(uploadPath), ".jpg");
String cover_upname = uploadName.replace("." + VideoUtil.getVideoFormat(uploadName), ".jpg");
String floder = ALIYUN_OSS_DIR_PREFIX.concat("/") + dir + "/video/frame1/";
thumb_file_url = videoUtil.getVideoCoverV2(ossUrl, floder, cover_upname);
/*if (VideoUtil.getVideoCover(uploadPath, cover_path, 1, "375*667")) {
if (uploadType.equals(1)) {
ossUrl = ConfigConstant.URL_BASE + "/admin/oss/upload/" + dir + "/" + uploadName; // 文件本地路径
} else if (uploadType.equals(2)) {
// oss 服务
try {
thumb_file_url = uploadObject2OSS(new File(cover_path), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(cover_upname), null, null, null);
ossUrl = uploadObject2OSS(new File(uploadPath), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat("/").concat(uploadName), null, null, null);
} catch (Exception e) {
e.printStackTrace();
logger.error("文件上传失败!", e);
return null;
}
} else if (uploadType.equals(4)) {
ossUrl = uploadObject4OSS(new File(uploadPath), TENGXUN_DEFAULT_DIR.concat("/").concat(dir).concat("/").concat(uploadName));
}
String media_duration = "";
try {
media_duration = VideoUtil.getFormatDuration(uploadPath);
} catch (IOException e) {
throw new ApiException(String.format(I18nUtil._("解析音视频时长异常url【%s】"), uploadPath));
}
//截图
//todo 放入upload中
String thumb_file_url = "";
if (VideoUtil.videoAllowFiles.contains(VideoUtil.getVideoFormat(uploadPath))) {
// String cover_path = uploadPath.replace("." + VideoUtil.getVideoFormat(uploadPath), ".jpg");
String cover_upname = uploadName.replace("." + VideoUtil.getVideoFormat(uploadName), ".jpg");
String floder = ALIYUN_OSS_DIR_PREFIX.concat("/") + dir + "/video/frame1/";
try {
thumb_file_url = videoUtil.getVideoCoverV2(ossUrl, floder, cover_upname);
} catch (Exception e) {
logger.error("获取视频封面失败: ", e);
thumb_file_url = "";
}
} else {
thumb_file_url = "";
}*/
/*if (VideoUtil.getVideoCover(uploadPath, cover_path, 1, "375*667")) {
try {
thumb_file_url = uploadObject2OSS(new File(cover_path), ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(cover_upname), null, null, null);
} catch (Exception e) {
e.printStackTrace();
thumb_file_url = "";
}
} else {
thumb_file_url = "";
}*/
}
// 保存文件信息
MediaDTO mediaDTO = new MediaDTO();
String name = file.getOriginalFilename();
long size = file.getSize();
mediaDTO.setMedia_name(name);
mediaDTO.setMedia_alt(name);
mediaDTO.setMedia_size(size);
mediaDTO.setMedia_url(ossUrl);
mediaDTO.setMedia_order(1);
mediaDTO.setMedia_path("/".concat(dir).concat("/").concat(uploadName));
mediaDTO.setMedia_domain(getOssUrlPrefix().concat(ALIYUN_OSS_DIR_PREFIX));
mediaDTO.setMedia_time(new Date());
mediaDTO.setMedia_desc("");
mediaDTO.setMedia_duration(media_duration);
shopStoreMediaService.saveMedia(mediaDTO, user);
Map result = new HashMap<>();
result.put("media_duration", media_duration);
result.put("media_url", ossUrl);
result.put("thumb", thumb_file_url);
return result;
} finally {
// 删除临时文件
try {
File tempFile = new File(uploadPath);
if (tempFile.exists()) {
tempFile.delete();
logger.info("临时文件已删除: {}", uploadPath);
}
} catch (Exception e) {
logger.warn("删除临时文件失败: {}", uploadPath, e);
}
}
// 保存文件信息
MediaDTO mediaDTO = new MediaDTO();
String name = file.getOriginalFilename();
long size = file.getSize();
mediaDTO.setMedia_name(name);
mediaDTO.setMedia_alt(name);
mediaDTO.setMedia_size(size);
mediaDTO.setMedia_url(ossUrl);
mediaDTO.setMedia_order(1);
mediaDTO.setMedia_path("/".concat(dir).concat("/").concat(fileName));
mediaDTO.setMedia_domain(getOssUrlPrefix().concat(ALIYUN_OSS_DIR_PREFIX));
mediaDTO.setMedia_time(new Date());
mediaDTO.setMedia_desc("");
mediaDTO.setMedia_duration(media_duration);
shopStoreMediaService.saveMedia(mediaDTO, user);
Map result = new HashMap<>();
result.put("media_duration", media_duration);
result.put("media_url", ossUrl);
result.put("thumb", thumb_file_url);
return result;
}
public Map upload(MultipartFile file, UserDto user) {
@ -580,7 +598,7 @@ public class OssServiceImpl implements OssService {
ObjectListing objectListing = ossCli.listObjects(listObjectsRequest);
List<String> commonPrefixes = objectListing.getCommonPrefixes();
logger.info(JSONUtil.toJsonStr(commonPrefixes));
return commonPrefixes;
return commonPrefixes;
}
@Override
@ -632,8 +650,7 @@ public class OssServiceImpl implements OssService {
}
/**
*
* @param ossFolder 如folder/example.txt
* @param ossFolder 如folder/example.txt
* @param localFolder /path/to/local/example.txt
* @return
*/

View File

@ -16,6 +16,7 @@ import org.springframework.stereotype.Component;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -24,14 +25,7 @@ import java.util.concurrent.TimeUnit;
@Component
public class VideoUtil {
private static Logger logger = LoggerFactory.getLogger(VideoUtil.class);
@Value("${aliyun.oss.dir.prefix}")
private String ALIYUN_OSS_DIR_PREFIX;
@Autowired
private OssService ossService;
private static final Logger logger = LoggerFactory.getLogger(VideoUtil.class);
public static List<String> videoAllowFiles = new ArrayList<String>() {{
add("flv");
add("swf");
@ -51,6 +45,10 @@ public class VideoUtil {
// add("wav");
// add("mid");
}};
@Value("${aliyun.oss.dir.prefix}")
private String ALIYUN_OSS_DIR_PREFIX;
@Autowired
private OssService ossService;
/**
* 得到语音或视频文件时长,单位秒 并格式化
@ -213,8 +211,7 @@ public class VideoUtil {
int mm = (temp % 3600) / 60;
int ss = (temp % 3600) % 60;
return hh != 0 ? ((hh < 10 ? ("0" + hh) : hh) + ":") : "" +
(mm < 10 ? ("0" + mm) : mm) + ":" +
return hh != 0 ? ((hh < 10 ? ("0" + hh) : hh) + ":") : (mm < 10 ? ("0" + mm) : mm) + ":" +
(ss < 10 ? ("0" + ss) : ss);
}
@ -227,24 +224,40 @@ public class VideoUtil {
* @return
*/
public String getVideoCoverV2(String video, String dir, String uploadName) {
InputStream inputStream = OssUtils.urlToInputSteam(video);
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream);
try {
grabber.start();
Frame frame = grabber.grabImage();
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.convert(frame);
Map map = OssUtils.toStreamMap(bufferedImage);
grabber.stop();
InputStream stream = (InputStream) map.get("stream");
Long file_size = Convert.toLong(map.get("file_size"));
InputStream inputStream = OssUtils.urlToInputSteam(video);
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream);
try {
grabber.start();
Frame frame = grabber.grabImage();
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.convert(frame);
Map map = OssUtils.toStreamMap(bufferedImage);
grabber.stop();
InputStream stream = (InputStream) map.get("stream");
Long file_size = Convert.toLong(map.get("file_size"));
// 上传至oos返回文件地址
return ossService.uploadObject2OSS(null, ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(uploadName), stream, uploadName, file_size);
} catch (IOException e) {
logger.error("失败原因:", e);
// 上传至oos返回文件地址
return ossService.uploadObject2OSS(null, ALIYUN_OSS_DIR_PREFIX.concat("/").concat(dir).concat(uploadName), stream, uploadName, file_size);
} catch (IOException e) {
logger.error("处理视频封面失败:", e);
} finally {
try {
if (grabber != null) {
grabber.stop();
}
} catch (Exception e) {
logger.warn("关闭视频抓取器失败:", e);
}
}
} catch (UnsatisfiedLinkError e) {
logger.error("JavaCV本地库加载失败请检查平台依赖配置", e);
} catch (NoClassDefFoundError e) {
logger.error("JavaCV类初始化失败请检查依赖配置", e);
} catch (Exception e) {
logger.error("获取视频封面时发生未知异常:", e);
}
return null;
return "";
}
}
@ -255,7 +268,7 @@ class InputStreamRunnable extends Thread {
public InputStreamRunnable(InputStream is, String _type) {
try {
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), "UTF-8"));
bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), StandardCharsets.UTF_8));
type = _type;
} catch (Exception ex) {
ex.printStackTrace();

View File

@ -59,6 +59,7 @@ import com.suisung.mall.shop.store.service.*;
import com.suisung.mall.shop.user.service.ShopUserFavoritesItemService;
import com.suisung.mall.shop.user.service.ShopUserProductBrowseService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -90,6 +91,7 @@ import static com.suisung.mall.common.utils.I18nUtil._;
* @author Xinze
* @since 2021-04-07
*/
@Slf4j
@Service
public class ShopProductItemServiceImpl extends BaseServiceImpl<ShopProductItemMapper, ShopProductItem> implements ShopProductItemService {
@ -2237,17 +2239,34 @@ public class ShopProductItemServiceImpl extends BaseServiceImpl<ShopProductItemM
}
/**
* 锁定库存
* 锁定SKU库存
*
* @param itemId
* @param cart_quantity
* @return
* @param itemId 商品SKU ID
* @param cartQuantity 购物车商品数量
* @return 影响的行数1表示锁定成功0表示锁定失败库存不足
*/
@Override
public int lockSkuStock(Long itemId, int cart_quantity) {
int flag = this.baseMapper.lockSkuStock(itemId, cart_quantity);
public int lockSkuStock(Long itemId, int cartQuantity) {
// 参数校验
if (itemId == null || cartQuantity <= 0) {
log.warn("锁定SKU库存参数异常: itemId={}, cartQuantity={}", itemId, cartQuantity);
return 0;
}
// 调用Mapper方法锁定库存
int affectedRows = this.baseMapper.lockSkuStock(itemId, cartQuantity);
// 清理SKU库存缓存
cleanSkuStockCache(itemId);
return flag;
// 记录库存锁定操作日志
if (affectedRows > 0) {
log.debug("SKU库存锁定成功: itemId={}, cartQuantity={}, affectedRows={}", itemId, cartQuantity, affectedRows);
} else {
log.warn("SKU库存锁定失败可能库存不足: itemId={}, cartQuantity={}", itemId, cartQuantity);
}
return affectedRows;
}
@Override

View File

@ -118,7 +118,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
ShopMchEntry shopMchEntry;
if (ObjectUtil.isEmpty(mchId)) {
shopMchEntry = shopMchEntryService.shopMerchEntryByStoreId(storeId);
shopMchEntry = shopMchEntryService.getShopMerchEntryByStoreId(storeId);
} else {
shopMchEntry = shopMchEntryService.shopMerchEntryById(mchId);
}
@ -488,6 +488,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
ShopStoreSfOrder shopStoreSfOrder = JSONUtil.toBean(sfExpressApiRes.get("result").toString(), ShopStoreSfOrder.class);
shopStoreSfOrder.setDev_id(sfCreateOrderReq.getDev_id());
shopStoreSfOrder.setOrder_status(SFExpressConstant.Cons_CreatedOrder);
shopStoreSfOrder.setShop_id(sfCreateOrderReq.getShop_id());
shopStoreSfOrder.setStatus_desc("已创建顺丰同城订单");
// 下单成功保存顺丰同城订单记录到 shop_store_sf_order 表里
@ -808,66 +809,98 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
/**
* 接收顺丰原因订单取消回调
* <p>
* 该方法用于处理顺丰同城配送订单取消的回调通知当顺丰因为某种原因取消订单时
* 会调用此接口通知商城系统商城系统需要更新订单状态为已取消并执行相关业务逻辑
* </p>
*
* @param jsonData
* @param sign
* @return
* @param jsonData 包含订单取消信息的JSON数据
* @param sign 请求签名用于验证请求的合法性
* @return ThirdApiRes 处理结果响应对象
* <ul>
* <li>成功: {"error_code": 0, "reason": "success"}</li>
* <li>失败: {"error_code": 错误码, "reason": "错误信息"}</li>
* </ul>
*/
@Override
public ThirdApiRes receiveCancelOrderNotify(String jsonData, String sign) {
// 参数校验
if (StrUtil.isBlank(jsonData) || StrUtil.isBlank(sign)) {
logger.warn("[顺丰订单取消回调通知] 缺少必要参数: jsonData或sign为空");
return new ThirdApiRes().fail(1003, "缺少必要参数!");
}
// 签名校验
if (!checkOpenSign(sign, jsonData)) {
logger.warn("[顺丰订单取消回调通知] 请求签名sign校验失败");
return new ThirdApiRes().fail(2002, "请求签名sign校验失败");
}
logger.info("接收顺丰原因订单取消回调返回的 JSON 数据:{}", jsonData);
logger.info("[顺丰订单取消回调通知] 接收回调数据: {}", jsonData);
// 更改顺丰同城订单状态
ShopStoreSfOrder shopStoreSfOrder = toShopStoreSfOrder(jsonData);
String shopOrderId = shopStoreSfOrder.getShop_order_id();
String sfOrderId = shopStoreSfOrder.getSf_order_id();
// 判断订单的状态是否已经取消了已取消不再执行
ShopStoreSfOrder shopStoreSfOrderExist = shopStoreSfOrderService.getBySfOrderId(sfOrderId);
if (shopStoreSfOrderExist != null && shopStoreSfOrderExist.getOrder_status() != null
&& (shopStoreSfOrderExist.getOrder_status().equals(StateCode.SF_ORDER_STATUS_CANCELED) ||
(shopStoreSfOrderExist.getOrder_status().equals(StateCode.SF_ORDER_STATUS_CANCELING)))) {
return new ThirdApiRes().success("success");
}
Boolean success = shopStoreSfOrderService.updateShopStoreSfOrderStatus(shopStoreSfOrder);
if (!success) {
return new ThirdApiRes().fail(-1, "状态处理失败!");
}
// 重要更改商城订单状态为已取消注意事务问题
success = shopOrderReturnService.sfExpressExpiredForceRefund(shopOrderId);
if (!success) {
return new ThirdApiRes().fail(-1, "取消订单业务处理失败!");
}
// 获取顺丰同城的物流轨迹
Map<String, Object> params = new HashMap<>();
params.put("order_id", sfOrderId);
ThirdApiRes feedRes = listOrderFeed(params);
if (feedRes != null && feedRes.getError_code().equals(0)) {
JSONObject result = JSONUtil.parseObj(feedRes.getResult());
if (result != null && result.get("feed") != null) {
shopStoreSfOrder.setFeed(JSONUtil.toJsonStr(result.get("feed")));
try {
// 解析并更新顺丰同城订单状态
ShopStoreSfOrder shopStoreSfOrder = toShopStoreSfOrder(jsonData);
if (shopStoreSfOrder == null) {
logger.error("[顺丰订单取消回调通知] 解析订单数据失败: jsonData={}", jsonData);
return new ThirdApiRes().fail(-1, "订单数据解析失败!");
}
String shopOrderId = shopStoreSfOrder.getShop_order_id();
String sfOrderId = shopStoreSfOrder.getSf_order_id();
logger.info("[顺丰订单取消回调通知] 处理订单取消: shopOrderId={} sfOrderId={}", shopOrderId, sfOrderId);
// 判断订单的状态是否已经取消了已取消不再执行
ShopStoreSfOrder shopStoreSfOrderExist = shopStoreSfOrderService.getBySfOrderId(sfOrderId);
if (shopStoreSfOrderExist != null && shopStoreSfOrderExist.getOrder_status() != null
&& (ObjectUtil.equal(shopStoreSfOrderExist.getOrder_status(), StateCode.SF_ORDER_STATUS_CANCELED) ||
ObjectUtil.equal(shopStoreSfOrderExist.getOrder_status(), StateCode.SF_ORDER_STATUS_CANCELING))) {
logger.info("[顺丰订单取消回调通知] 订单已处于取消状态,无需重复处理: sfOrderId={} status={}",
sfOrderId, shopStoreSfOrderExist.getOrder_status());
return new ThirdApiRes().success("success");
}
// 更新顺丰订单状态
Boolean success = shopStoreSfOrderService.updateShopStoreSfOrderStatus(shopStoreSfOrder);
if (!success) {
logger.error("[顺丰订单取消回调通知] 更新顺丰订单状态失败: sfOrderId={}", sfOrderId);
return new ThirdApiRes().fail(-1, "状态处理失败!");
}
logger.debug("[顺丰订单取消回调通知] 顺丰订单状态更新成功: sfOrderId={}", sfOrderId);
// 重要更改商城订单状态为已取消注意事务问题
success = shopOrderReturnService.sfExpressExpiredForceRefund(shopOrderId);
if (!success) {
logger.error("[顺丰订单取消回调通知] 取消商城订单业务处理失败: shopOrderId={}", shopOrderId);
return new ThirdApiRes().fail(-1, "取消订单业务处理失败!");
}
logger.debug("[顺丰订单取消回调通知] 商城订单取消处理成功: shopOrderId={}", shopOrderId);
// 获取顺丰同城的物流轨迹
Map<String, Object> params = new HashMap<>();
params.put("order_id", sfOrderId);
ThirdApiRes feedRes = listOrderFeed(params);
if (feedRes != null && ObjectUtil.equal(feedRes.getError_code(), 0)) {
JSONObject result = JSONUtil.parseObj(feedRes.getResult());
if (result != null && result.get("feed") != null) {
shopStoreSfOrder.setFeed(JSONUtil.toJsonStr(result.get("feed")));
logger.debug("[顺丰订单取消回调通知] 获取物流轨迹成功: sfOrderId={}", sfOrderId);
}
}
// 个推推送消息
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);
payload.put("orderId", shopOrderId);
pushMessageService.noticeMerchantEmployeeOrderAction(null, shopOrderId, "您有一笔取消订单", "您有一笔取消订单[" + shopOrderId + "],请及时处理。", payload);
logger.info("[顺丰订单取消回调通知] 处理完成: shopOrderId={} sfOrderId={}", shopOrderId, sfOrderId);
return new ThirdApiRes().success("success");
} catch (Exception e) {
logger.error("[顺丰订单取消回调通知] 处理过程中发生异常", e);
return new ThirdApiRes().fail(-1, "系统处理异常: " + e.getMessage());
}
// 个推推送消息
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);
payload.put("orderId", shopOrderId);
pushMessageService.noticeMerchantEmployeeOrderAction(null, shopOrderId, "您有一笔取消订单", "您有一笔取消订单[" + shopOrderId + "],请及时处理。", null);
return new ThirdApiRes().success("success");
}
/**
@ -935,8 +968,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
pushRemark = "配送员已接单。";
// 上传发货信息到微信
wxOrderShippingService.uploadShippingInfoToWx(2, shopStoreSfOrder.getShop_order_id());
wxOrderShippingService.uploadShippingInfoToWx(2, shopOrderId);
} else if (shopStoreSfOrder.getOrder_status().equals(StateCode.SF_ORDER_STATUS_ARRIVED)) {
// 顺丰同城状态12-配送员到店;
// 商城订单状态 2020-待配货 2030; //待发货
@ -946,7 +978,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
pushRemark = "配送员已到店。";
// 上传发货信息到微信
wxOrderShippingService.uploadShippingInfoToWx(2, shopStoreSfOrder.getShop_order_id());
wxOrderShippingService.uploadShippingInfoToWx(2, shopOrderId);
} else if (shopStoreSfOrder.getOrder_status().equals(StateCode.SF_ORDER_STATUS_RECEIVED)) {
// 顺丰同城状态15-配送员配送中已取货
// 商城订单状态 2030-待发货 2040-已发货/待收货确认
@ -954,26 +986,26 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
orderIsOutStatus = 0;
orderIsShippedStatus = StateCode.ORDER_SHIPPED_STATE_YES; // 已发货
pushRemark = "配送员已取货。";
// 上传发货信息到微信
wxOrderShippingService.uploadShippingInfoToWx(2, shopOrderId);
} else if (shopStoreSfOrder.getOrder_status().equals(StateCode.SF_ORDER_STATUS_FINISH)) {
// 顺丰同城状态17-配送员妥投完单;
// orderStatus = StateCode.ORDER_STATE_FINISH;
pushRemark = "已完成配送";
orderStatus = StateCode.ORDER_STATE_RECEIVED; //已签收
// 通知微信用户确认收货(同城配送不能调用微信的确认收货)
// wxOrderShippingService.notifyConfirmReceive(shopStoreSfOrder.getShop_order_id());
// 订单确认收货
shopOrderBaseService.receive(shopStoreSfOrder.getShop_order_id(), null);
// 不要提前 订单确认收货等微信拉卡拉确认通知
// try {
// shopOrderBaseService.receive(shopOrderId, null);
// } catch (Exception e) {
// logger.error("订单确认收货失败!", e);
// }
}
success = shopOrderInfoService.changeOrderStatus(shopStoreSfOrder.getShop_order_id(), orderStatus, orderIsOutStatus, orderIsShippedStatus);
if (!success) {
throw new ApiException(I18nUtil._("状态处理失败!"));
}
// 状态更改之后 SSE 监听服务发送更改的数据
// logger.debug("准备发送SSE消息...");
// SseEmitterUtil.sendMessage(shopStoreSfOrder.getSf_order_id(), jsonData);
// logger.debug("向 SSE 通道 {} 发送了:{}", shopStoreSfOrder.getSf_order_id(), jsonData);
shopOrderInfoService.changeOrderStatus(shopOrderId, orderStatus, orderIsOutStatus, orderIsShippedStatus);
// 消息推送
JSONObject payload = new JSONObject();
@ -1010,9 +1042,9 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
return new ThirdApiRes().fail(-1, "返回数据转换失败!");
}
String orderId = shopStoreSfOrder.getShop_order_id();
ShopStoreSfOrder order = shopStoreSfOrderService.getBySfOrderId(orderId);
String sfOrderId = shopStoreSfOrder.getSf_order_id();
ShopStoreSfOrder order = shopStoreSfOrderService.getBySfOrderId(sfOrderId);
if (order == null) {
return new ThirdApiRes().fail(-1, "订单不存在!");
}
@ -1050,21 +1082,20 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
// 更改商城订单状态为已完成注意事务问题
// 配送员已取单配送中
// 商城订单状态 040-已发货/待收货确认 2060-已完成/已签收
success = shopOrderInfoService.changeOrderStatus(shopStoreSfOrder.getShop_order_id(), StateCode.ORDER_STATE_FINISH, 0, 0);
// 商城订单状态 040-已发货/待收货确认 2060-已完成/已签收 (RMK 这里可能有点问题需要等待用户确认收货才能设置订单已完成)
// success = shopOrderInfoService.changeOrderStatus(shopStoreSfOrder.getShop_order_id(), StateCode.ORDER_STATE_FINISH, 0, 0);
success = shopOrderInfoService.changeOrderStatus(shopStoreSfOrder.getShop_order_id(), StateCode.ORDER_STATE_RECEIVED, 0, 0);
if (!success) {
throw new ApiException(I18nUtil._("状态处理失败!"));
}
// 状态更改之后 SSE 监听服务发送更改的数据
// logger.debug("准备发送SSE消息...");
// SseEmitterUtil.sendMessage(shopStoreSfOrder.getSf_order_id(), jsonData);
// 通知微信用户确认收货同城配送不能调用该微信接口
// wxOrderShippingService.notifyConfirmReceive(shopStoreSfOrder.getShop_order_id());
// 订单确认收货
shopOrderBaseService.receive(shopStoreSfOrder.getShop_order_id(), null);
// shopOrderBaseService.receive(shopStoreSfOrder.getShop_order_id(), null);
String orderId = shopStoreSfOrder.getShop_order_id();
// 消息推送
JSONObject payload = new JSONObject();

View File

@ -73,7 +73,7 @@ public class StoreController extends BaseControllerImpl {
@RequestMapping(value = "/lists", method = RequestMethod.GET)
public CommonResult lists(@RequestParam(name = "page", defaultValue = "1") Integer page,
@RequestParam(name = "rows", defaultValue = "10") Integer rows) {
return CommonResult.success(shopStoreBaseService.getStoreList(page, rows,new HashMap<>()));
return CommonResult.success(shopStoreBaseService.getStoreList(page, rows, new HashMap<>()));
}
@ApiOperation(value = "列出店铺分类", notes = "列出店铺分类")
@ -314,26 +314,4 @@ public class StoreController extends BaseControllerImpl {
}
}
// /**
// * 列表查询
// * @return
// */
// @ApiOperation(value = "店铺分类表-分类强调区别, 类型强调共性-分页列表查询", notes = "店铺分类表-分类强调区别, 类型强调共性-分页列表查询")
// @RequestMapping(value = "/categoryTree", method = RequestMethod.GET)
// public CommonResult categoryTree(ShopBaseStoreCategory category) {
// QueryWrapper<ShopBaseStoreCategory> queryWrapper = new QueryWrapper<>();
// if (category.getStore_category_parent_id() != null)
// queryWrapper.eq("store_category_parent_id", category.getStore_category_parent_id());
//
// return CommonResult.success(shopBaseStoreCategoryService.getMobileCategoryTree(queryWrapper));
// }
// @ApiOperation(value = "根据分类查询店铺", notes = "列表数据")
// @RequestMapping(value = "/listStores", method = RequestMethod.GET)
// public Page<ShopStoreBase> listStores(@RequestParam(name = "page", defaultValue = "1") Integer page,
// @RequestParam(name = "rows", defaultValue = "10") Integer rows) {
// return shopStoreBaseService.getMobileStoreList(page, rows);
// }
}

View File

@ -13,6 +13,7 @@ import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.modules.store.ShopMchEntry;
import org.springframework.data.util.Pair;
import java.math.BigDecimal;
import java.util.List;
public interface ShopMchEntryService {
@ -114,7 +115,7 @@ public interface ShopMchEntryService {
* @param storeId
* @return
*/
ShopMchEntry shopMerchEntryByStoreId(Integer storeId);
ShopMchEntry getShopMerchEntryByStoreId(Integer storeId);
/**
@ -214,7 +215,7 @@ public interface ShopMchEntryService {
* @param storeId
* @return
*/
Boolean updateMerchEntryStoreId(Long id, Integer storeId);
Boolean updateMerchEntryStoreStatus(Long id, Integer storeId);
/**
* 更新商户入驻信息的拉卡拉电子合同相关信息
@ -227,7 +228,7 @@ public interface ShopMchEntryService {
* @param lklElectronicContractFilePath 拉卡拉电子合同文件路径
* @return 更新成功返回 true, 失败返回 false
*/
Boolean updateMerchantLklElectronicContractInfo(Long merchantId, String lklElectronicContractNo, String lklElectronicContractName, String lklElectronicContractResultUrl, String contractDownloadUrl, String lklElectronicContractFilePath);
Boolean updateMerchantLklEContractInfo(Long merchantId, String lklElectronicContractNo, String lklElectronicContractName, String lklElectronicContractResultUrl, String contractDownloadUrl, String lklElectronicContractFilePath);
/**
* 更新商家入驻申请的审批状态和审批备注
@ -334,4 +335,17 @@ public interface ShopMchEntryService {
* @return boolean 是否成功修复至少一个商户信息
*/
Boolean checkAndFixMchStoreInfo(String loginMobile);
/**
* 获取商户入驻分账比例
* <p>
* 该分账比例是实际的分账百分比最终提交给拉卡拉的合同的分账比例是 20%
*
* @param mch1stBizCategory 商户一级业务分类
* @param mch2ndBizCategory 商户二级业务分类
* @param srcRatio 源比例
* @param defaultRatio 无值的时候默认比例
* @return 分账比例
*/
BigDecimal getMchEntryRatioOrDefault(Integer mch1stBizCategory, Integer mch2ndBizCategory, BigDecimal srcRatio, BigDecimal defaultRatio);
}

View File

@ -17,6 +17,14 @@ import java.util.Map;
*/
public interface ShopStoreAnalyticsService extends IBaseService<ShopStoreAnalytics> {
/**
* 根据店铺id查询店铺统计信息
*
* @param storeId
* @return
*/
ShopStoreAnalytics getByStoreId(Integer storeId);
List<Map> getAnalytics(List<Integer> store_id);
boolean saveProductAnalyticsNum(Integer store_id);

View File

@ -14,5 +14,13 @@ import com.suisung.mall.core.web.service.IBaseService;
*/
public interface ShopStoreCompanyService extends IBaseService<ShopStoreCompany> {
/**
* 获取店铺公司信息
*
* @param storeId
* @return
*/
ShopStoreCompany getCompany(Integer storeId);
boolean saveOrUpdateCompany(ShopStoreCompany shopStoreCompany);
}

View File

@ -38,6 +38,17 @@ public interface ShopStoreEmployeeService extends IBaseService<ShopStoreEmployee
*/
List<Integer> selectEmployeeByStoreId(Integer storeId, String rightsGroupName);
/**
* 根据店铺Id和员工Id获取员工信息
*
* @param storeId
* @param userId
* @param isAdmin
* @return
*/
List<ShopStoreEmployee> selectEmployeeByCondition(Integer storeId, Integer userId, Boolean isAdmin);
/**
* 根据店铺Id获取店铺的所有员工的个推 CID 列表
*

View File

@ -35,6 +35,22 @@ public class ShopStoreAnalyticsServiceImpl extends BaseServiceImpl<ShopStoreAnal
@Autowired
private ShopProductBaseService shopProductBaseService;
/**
* 根据店铺id查询店铺统计信息
*
* @param storeId
* @return
*/
@Override
public ShopStoreAnalytics getByStoreId(Integer storeId) {
try {
return getById(storeId);
} catch (Exception e) {
return null;
}
}
/**
* 根据主键值从数据库读取数据, 必须获取主键后再读取数据
*
@ -103,15 +119,24 @@ public class ShopStoreAnalyticsServiceImpl extends BaseServiceImpl<ShopStoreAnal
@Override
public boolean saveProductAnalyticsNum(Integer store_id) {
ShopStoreAnalytics analytics = new ShopStoreAnalytics();
long store_product_new_num = shopProductBaseService.getProductNum(StateCode.PRODUCT_STATE_NORMAL, store_id, -30, null);
long store_product_num = shopProductBaseService.getProductNum(StateCode.PRODUCT_STATE_NORMAL, store_id, null, null);
ShopStoreAnalytics analytics = new ShopStoreAnalytics();
analytics.setStore_id(store_id);
analytics.setStore_product_new_num(store_product_new_num);
analytics.setStore_product_num(store_product_num);
return saveOrUpdate(analytics);
// 先尝试查询是否存在记录
ShopStoreAnalytics existingAnalytics = getById(store_id);
if (existingAnalytics != null) {
// 如果记录存在使用updateById更新
return updateById(analytics);
} else {
// 如果记录不存在使用insert插入
return save(analytics);
}
}
}

View File

@ -2033,7 +2033,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 添加默认客户等级
InvoicingCustomerLevel invoicingCustomerLevel = new InvoicingCustomerLevel();
invoicingCustomerLevel.setCustomer_level_name(I18nUtil._("普通(系统默认,不可删除)"));
invoicingCustomerLevel.setCustomer_level_discountrate(new BigDecimal("100"));
invoicingCustomerLevel.setCustomer_level_discountrate(new BigDecimal(100));
invoicingCustomerLevel.setCustomer_level_is_buildin(1);
invoicingCustomerLevel.setCustomer_level_desc("");
@ -2375,7 +2375,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 打包费
BigDecimal packingFee = Convert.toBigDecimal(getParameter("packing_fee"));
if (packingFee == null || packingFee.compareTo(BigDecimal.ZERO) <= 0) {
base.setPacking_fee(BigDecimal.ZERO);
packingFee = BigDecimal.ZERO;
}
if (packingFee.compareTo(new BigDecimal("10")) > 0) {
@ -2383,6 +2383,13 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
base.setPacking_fee(packingFee);
Integer ringtoneIsEnable = Convert.toInt(getParameter("ringtone_is_enable"));
if (ringtoneIsEnable == null || ringtoneIsEnable <= 0) {
ringtoneIsEnable = CommonConstant.Enable;
}
base.setRingtone_is_enable(ringtoneIsEnable);
// 百度坐标系BD09经纬度 转出 火星坐标系GCJ02经纬度 因为数据库保存的经纬度统一是GCJ02经纬度所以需要转换
base = bd09ToGcj02Gps(base);
@ -2538,7 +2545,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 打包费
BigDecimal packingFee = Convert.toBigDecimal(getParameter("packing_fee"));
if (packingFee == null || packingFee.compareTo(BigDecimal.ZERO) <= 0) {
base.setPacking_fee(BigDecimal.ZERO);
packingFee = BigDecimal.ZERO;
}
if (packingFee.compareTo(new BigDecimal("10")) > 0) {
@ -2546,6 +2553,13 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
base.setPacking_fee(packingFee);
Integer ringtoneIsEnable = Convert.toInt(getParameter("ringtone_is_enable"));
if (ringtoneIsEnable == null || ringtoneIsEnable <= 0) {
ringtoneIsEnable = CommonConstant.Enable;
}
base.setRingtone_is_enable(ringtoneIsEnable);
// 百度坐标系BD09经纬度 转出 火星坐标系GCJ02经纬度 因为数据库保存的经纬度统一是GCJ02经纬度所以需要转换
base = bd09ToGcj02Gps(base);
@ -2734,6 +2748,14 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
if (CheckUtil.isNotEmpty(store_id)) {
ShopStoreBase shopStoreBase = get(store_id);
if (shopStoreBase == null) {
throw new ApiException("无法找到店铺信息!");
}
if (CheckUtil.isEmpty(shopStoreBase.getRingtone_is_enable())) {
shopStoreBase.setRingtone_is_enable(CommonConstant.Enable);
}
row = Convert.toMap(Object.class, Object.class, shopStoreBase);
ShopStoreInfo shopStoreInfo = shopStoreInfoService.get(store_id);
if (shopStoreInfo != null) {
@ -3046,17 +3068,17 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
params.put("countyId", countyId);
params.put("userLng", userLng);
params.put("userLat", userLat);
List<Integer> storeCategoryIds=new ArrayList<>();
List<Integer> storeCategoryIds = new ArrayList<>();
QueryWrapper<ShopBaseStoreCategory> storeCategoryQueryWrapper = new QueryWrapper<>();
storeCategoryQueryWrapper.eq("store_category_parent_id",storeCategoryId);
List<ShopBaseStoreCategory> shopBaseStoreCategories= shopBaseStoreCategoryService.list(storeCategoryQueryWrapper);
if(!shopBaseStoreCategories.isEmpty()){
storeCategoryQueryWrapper.eq("store_category_parent_id", storeCategoryId);
List<ShopBaseStoreCategory> shopBaseStoreCategories = shopBaseStoreCategoryService.list(storeCategoryQueryWrapper);
if (!shopBaseStoreCategories.isEmpty()) {
storeCategoryIds.add(storeCategoryId);
shopBaseStoreCategories.forEach(shopBaseStoreCategory -> {
storeCategoryIds.add(shopBaseStoreCategory.getStore_category_id());
});
params.put("storeCategoryIds", storeCategoryIds);
}else {
} else {
params.put("storeCategoryId", storeCategoryId);
}
@ -3165,7 +3187,12 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
shopStoreBase.setUser_id(userId); // 店铺管理员用户Id
shopStoreBase.setStore_name(shopMchEntry.getStore_name());
shopStoreBase.setStore_category_id(shopMchEntry.getBiz_category()); // 重要店铺分类id对应 shop_base_store_category 表的分类
shopStoreBase.setSplit_ratio(shopMchEntry.getSplit_ratio()); // 分账比例
shopStoreBase.setStore_2nd_category_id(shopMchEntry.getBiz_second_category());
// 分账比例最终提交给拉卡拉的分账比例是 20%
BigDecimal splitRatio = shopMchEntryService.getMchEntryRatioOrDefault(shopMchEntry.getBiz_category(), shopMchEntry.getBiz_second_category(), shopMchEntry.getSplit_ratio(), new BigDecimal(94));
shopStoreBase.setSplit_ratio(splitRatio); // 分账比例
shopStoreBase.setPacking_fee(BigDecimal.ZERO);// 默认0元打包费
String storeFacadeImage = shopMchEntry.getFront_facade_image();
String storeLogoImage = shopMchEntry.getStore_logo();
@ -3253,75 +3280,85 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 初始化默认公司信息避免商家编辑不了申请入驻即使是个人类型的也需要保存相关应有信息公司信息不能空着
// shop_store_company
ShopStoreCompany shopStoreCompany = new ShopStoreCompany();
shopStoreCompany.setUser_id(userId);
shopStoreCompany.setStore_id(storeId);
ShopStoreCompany shopStoreCompany = shopStoreCompanyService.getCompany(storeId);
if (shopStoreCompany == null) {
shopStoreCompany.setUser_id(userId);
shopStoreCompany.setStore_id(storeId);
// 联系人
shopStoreCompany.setContacts_name(shopMchEntry.getContact_name());
shopStoreCompany.setContacts_phone(contact_mobile);
// 联系人
shopStoreCompany.setContacts_name(shopMchEntry.getContact_name());
shopStoreCompany.setContacts_phone(contact_mobile);
// 公司名
String companyName = StrUtil.isBlank(shopMchEntry.getBiz_license_company()) ? shopMchEntry.getStore_name() : shopMchEntry.getBiz_license_company();
shopStoreCompany.setCompany_name(companyName);
shopStoreCompany.setCompany_area(shopStoreBase.getStore_area());
shopStoreCompany.setCompany_address(shopMchEntry.getStore_address());
// 公司名
String companyName = StrUtil.isBlank(shopMchEntry.getBiz_license_company()) ? shopMchEntry.getStore_name() : shopMchEntry.getBiz_license_company();
shopStoreCompany.setCompany_name(companyName);
shopStoreCompany.setCompany_area(shopStoreBase.getStore_area());
shopStoreCompany.setCompany_address(shopMchEntry.getStore_address());
// 营业执照
shopStoreCompany.setBusiness_id(shopMchEntry.getBiz_license_number());
shopStoreCompany.setBusiness_license_electronic(shopMchEntry.getBiz_license_image());
// 营业执照
shopStoreCompany.setBusiness_id(shopMchEntry.getBiz_license_number());
shopStoreCompany.setBusiness_license_electronic(shopMchEntry.getBiz_license_image());
// 企业法人
shopStoreCompany.setLegal_person(shopMchEntry.getLegal_person_name());
shopStoreCompany.setLegal_person_number(shopMchEntry.getLegal_person_id_number());
shopStoreCompany.setLegal_person_electronic(shopMchEntry.getLegal_person_id_images());
// 企业法人
shopStoreCompany.setLegal_person(shopMchEntry.getLegal_person_name());
shopStoreCompany.setLegal_person_number(shopMchEntry.getLegal_person_id_number());
shopStoreCompany.setLegal_person_electronic(shopMchEntry.getLegal_person_id_images());
// 银行对公账号
shopStoreCompany.setBank_account_name(shopMchEntry.getAccount_holder_name());
shopStoreCompany.setBank_account_number(shopMchEntry.getAccount_number());
shopStoreCompany.setBank_name(shopMchEntry.getBank_name());
// 银行对公账号
shopStoreCompany.setBank_account_name(shopMchEntry.getAccount_holder_name());
shopStoreCompany.setBank_account_number(shopMchEntry.getAccount_number());
shopStoreCompany.setBank_name(shopMchEntry.getBank_name());
Date today = new Date(); // 当前时间
shopStoreCompany.setOrganization_code_start(today);
shopStoreCompany.setOrganization_code_end(DateUtil.offsetDay(today, 365 * 5));// 五年
shopStoreCompany.setEstablish_date(today);
shopStoreCompany.setBusiness_licence_start(today);
shopStoreCompany.setBusiness_licence_end(DateUtil.offsetDay(today, 365 * 10));
Date today = new Date(); // 当前时间
shopStoreCompany.setOrganization_code_start(today);
shopStoreCompany.setOrganization_code_end(DateUtil.offsetDay(today, 365 * 5));// 五年
shopStoreCompany.setEstablish_date(today);
shopStoreCompany.setBusiness_licence_start(today);
shopStoreCompany.setBusiness_licence_end(DateUtil.offsetDay(today, 365 * 10));
shopStoreCompany.setCompany_description(shopMchEntry.getStore_name());
shopStoreCompany.setStore_class_ids("");
shopStoreCompany.setStore_class_names("");
shopStoreCompany.setStore_class_commission("");
shopStoreCompany.setCompany_description(shopMchEntry.getStore_name());
shopStoreCompany.setStore_class_ids("");
shopStoreCompany.setStore_class_names("");
shopStoreCompany.setStore_class_commission("");
if (!shopStoreCompanyService.save(shopStoreCompany)) {
logger.error("生成店铺:新增店铺公司失败");
if (allowThrown) {
throw new ApiException(I18nUtil._("新增店铺公司失败"));
if (!shopStoreCompanyService.save(shopStoreCompany)) {
logger.error("生成店铺:新增店铺公司失败");
if (allowThrown) {
throw new ApiException(I18nUtil._("新增店铺公司失败"));
}
return Pair.of(0, "新增店铺公司失败");
}
return Pair.of(0, "新增店铺公司失败");
}
List<ShopStoreEmployee> shopStoreEmployees = shopStoreEmployeeService.selectEmployeeByCondition(storeId, null, null);
QueryWrapper<ShopStoreEmployee> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("store_id", storeId).eq("user_id", userId).eq("employee_is_admin", CommonConstant.Enable);
if (shopStoreEmployeeService.count(queryWrapper) <= 0) {
ShopStoreEmployee shopStoreEmployee = new ShopStoreEmployee();
shopStoreEmployee.setStore_id(storeId);
shopStoreEmployee.setUser_id(userId);
shopStoreEmployee.setRights_group_id(""); // 店铺管理员,店铺
shopStoreEmployee.setEmployee_is_kefu(CommonConstant.Enable);
if (CollUtil.isEmpty(shopStoreEmployees)) { // 添加管理员
// shop_store_employee 店铺员工添加管理员
ShopStoreEmployee shopStoreEmployee = new ShopStoreEmployee();
shopStoreEmployee.setStore_id(storeId);
shopStoreEmployee.setUser_id(userId);
shopStoreEmployee.setRights_group_id(""); // 店铺管理员,店铺
shopStoreEmployee.setEmployee_is_admin(CommonConstant.Enable);
shopStoreEmployee.setEmployee_is_kefu(CommonConstant.Enable);
if (!shopStoreEmployeeService.save(shopStoreEmployee)) {
logger.error("生成店铺:新增店铺员工失败");
if (allowThrown) {
throw new ApiException(I18nUtil._("新增店铺员工失败"));
}
return Pair.of(0, "新增店铺员工失败");
} else { // 添加店员或管理员
shopStoreEmployees = shopStoreEmployeeService.selectEmployeeByCondition(storeId, userId, null);
if (CollUtil.isEmpty(shopStoreEmployees)) {
shopStoreEmployee.setEmployee_is_admin(CommonConstant.Disable);
} else {
shopStoreEmployee.setEmployee_is_admin(shopStoreEmployees.get(0).getEmployee_is_admin());
}
}
if (!shopStoreEmployeeService.save(shopStoreEmployee)) {
logger.error("生成店铺:新增店铺员工失败");
if (allowThrown) {
throw new ApiException(I18nUtil._("新增店铺员工失败"));
}
return Pair.of(0, "新增店铺员工失败");
}
// 生成店铺的太阳码 2025-03-31
if (StrUtil.isBlank(shopStoreBase.getWx_qrcode())) {
Pair<String, String> resp = wxQrCodeService.genUnlimitedWxQrCode("pagesub/index/store", "store_id=" + storeId);
@ -3331,13 +3368,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
// 注意关联店铺到用户给用户增加店铺管理员权限
initStoreExtraInfo(userId, storeId);
initStoreExtraInfo(userId, storeId, allowThrown);
// 更改店铺Id
shopMchEntryService.updateMerchEntryStoreId(shopMchEntry.getId(), storeId);
// 初始化同城配送默认设置
// storeSameCityTransportBaseService.initDefaultSameCityTransport(storeId);
// 更改店铺Id和状态
shopMchEntryService.updateMerchEntryStoreStatus(shopMchEntry.getId(), storeId);
// 立即创建顺丰店铺附带初始化同城配送默认设置
String[] areaNames = StrUtil.isNotBlank(shopMchEntry.getStore_area()) ? shopMchEntry.getStore_area().split("/") : new String[0];
@ -3360,7 +3394,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
* @param userId
* @param storeId
*/
private void initStoreExtraInfo(Integer userId, Integer storeId) {
private void initStoreExtraInfo(Integer userId, Integer storeId, Boolean allowThrown) {
if (ObjectUtil.isNull(userId) || ObjectUtil.isNull(storeId)) {
return;
}
@ -3368,10 +3402,16 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
Date today = new Date(); // 当前时间
// 初始化时添加一条店铺分析信息
ShopStoreAnalytics store_analytics_column = new ShopStoreAnalytics();
store_analytics_column.setStore_id(storeId);
if (!shopStoreAnalyticsService.saveOrUpdate(store_analytics_column)) {
throw new ApiException(ResultCode.FAILED);
ShopStoreAnalytics storeAnalytics = shopStoreAnalyticsService.getByStoreId(storeId);
if (storeAnalytics == null) {
storeAnalytics = new ShopStoreAnalytics();
storeAnalytics.setStore_id(storeId);
if (!shopStoreAnalyticsService.add(storeAnalytics)) {
if (allowThrown) {
throw new ApiException(ResultCode.FAILED);
}
log.error("初始化店铺分析信息失败");
}
}
// 初始化店铺商品标签
@ -3390,7 +3430,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
if (CollUtil.isNotEmpty(store_product_tag_row)) {
if (!shopStoreProductTagService.saveOrUpdate(store_product_tag_row)) {
throw new ApiException(ResultCode.FAILED);
if (allowThrown) {
throw new ApiException(ResultCode.FAILED);
}
log.error("初始化店铺商品标签失败");
}
}
@ -3402,7 +3445,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
invoicingWarehouseBase.setWarehouse_number("");
invoicingWarehouseBase.setWarehouse_contact("");
if (!invoicingWarehouseBaseService.saveOrUpdate(invoicingWarehouseBase)) {
throw new ApiException(ResultCode.FAILED);
if (allowThrown) {
throw new ApiException(ResultCode.FAILED);
}
log.error("初始化默认店铺仓库失败");
}
//初始化默认权限
@ -3490,7 +3536,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
shopStoreEmployeeRightsGroups.add(extension2);
if (!shopStoreEmployeeRightsGroupService.saveOrUpdate(shopStoreEmployeeRightsGroups)) {
throw new ApiException(ResultCode.FAILED);
if (allowThrown) {
throw new ApiException(ResultCode.FAILED);
}
log.error("初始化店铺员工默认权限失败");
}
// 添加店铺到用户
@ -3504,7 +3553,9 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
.set("employee_is_admin", CommonConstant.Enable)
.set("employee_is_kefu", CommonConstant.Enable);
if (!shopStoreEmployeeService.update(queryWrapper)) {
// throw new ApiException(I18nUtil._("设置店铺管理员权限失败"));
if (allowThrown) {
throw new ApiException(I18nUtil._("设置店铺管理员权限失败"));
}
log.error("设置店铺管理员权限失败!");
}
@ -3518,7 +3569,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
String storeIds = appendStoreIdToAccount(userId, storeId);
accountUserBase.setStore_ids(storeIds); // 重要给用户添加上这个店铺的归属权
if (!accountService.saveOrUpdateUserBase(accountUserBase)) {
throw new ApiException(I18nUtil._("店铺关联到用户失败"));
if (allowThrown) {
throw new ApiException(I18nUtil._("店铺关联到用户失败"));
}
log.error("店铺关联到用户失败!");
}
// 添加默认运输模板
@ -3530,7 +3584,10 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
shopStoreTransportType.setTransport_type_freight_free(BigDecimal.ZERO); // 免运费额度
shopStoreTransportType.setTransport_type_free(1);
if (!shopStoreTransportTypeService.saveOrUpdate(shopStoreTransportType)) {
throw new ApiException(I18nUtil._("添加运输模板失败"));
if (allowThrown) {
throw new ApiException(I18nUtil._("添加运输模板失败"));
}
log.error("添加运输模板失败!");
}
// 店铺配置
@ -3569,18 +3626,24 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
shopStoreConfig.setSc_festival_amount_upper(BigDecimal.ZERO);
shopStoreConfig.setSc_festival_float_proportion(BigDecimal.ZERO);
if (!shopStoreConfigService.saveOrUpdate(shopStoreConfig)) {
throw new ApiException(I18nUtil._("添加订单流转配置失败"));
if (allowThrown) {
throw new ApiException(I18nUtil._("添加订单流转配置失败"));
}
log.error("添加订单流转配置失败!");
}
// 添加默认客户等级
InvoicingCustomerLevel invoicingCustomerLevel = new InvoicingCustomerLevel();
invoicingCustomerLevel.setCustomer_level_name(I18nUtil._("普通(系统默认,不可删除)"));
invoicingCustomerLevel.setCustomer_level_discountrate(new BigDecimal("100"));
invoicingCustomerLevel.setCustomer_level_discountrate(new BigDecimal(100));
invoicingCustomerLevel.setCustomer_level_is_buildin(1);
invoicingCustomerLevel.setCustomer_level_desc("");
if (!invoicingCustomerLevelService.saveOrUpdate(invoicingCustomerLevel)) {
throw new ApiException(I18nUtil._("添加默认客户等级失败"));
if (allowThrown) {
throw new ApiException(I18nUtil._("添加默认客户等级失败"));
}
log.error("添加默认客户等级失败!");
}
}
@ -3642,50 +3705,54 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
* 获取店铺的分账比例
* 根据店铺的大分小分类来计算分账比例
*
* @param storeId
* @param storeId 店铺ID
* @param reCalculate 是否根据店铺的分类来重新计算
* @return 平台分账比例的数值
*/
@Override
public BigDecimal getStoreSplitRatio(Integer storeId, boolean reCalculate) {
BigDecimal defaultSplitRatio = new BigDecimal("100");
log.debug("开始获取店铺分账比例storeId={}, reCalculate={}", storeId, reCalculate);
// 定义默认分账比例
final BigDecimal defaultSplitRatio = BigDecimal.valueOf(94);
if (storeId == null || storeId <= 0) {
log.warn("店铺ID无效使用默认分账比例: {}", defaultSplitRatio);
return defaultSplitRatio;
}
BigDecimal splitRatio = null;
// 获取店铺基础信息
ShopStoreBase shopStoreBase = get(storeId);
if (shopStoreBase == null) {
return defaultSplitRatio;
}
log.debug("获取店铺基础信息完成shopStoreBase={}",
shopStoreBase != null ? shopStoreBase.getStore_id() : null);
// 获取店铺已配置的分账比例
BigDecimal splitRatio = shopStoreBase.getSplit_ratio();
if (shopStoreBase != null) {
// 获取店铺已配置的分账比例
splitRatio = CheckUtil.isEmpty(shopStoreBase.getSplit_ratio()) ? null : shopStoreBase.getSplit_ratio();
log.debug("从店铺基础信息获取分账比例: {}", splitRatio);
}
// 如果没有配置或需要重新计算则基于店铺分类获取比例
if (reCalculate || splitRatio == null) {
Integer storeCategoryId = shopStoreBase.getStore_category_id();
if (storeCategoryId == null) {
return defaultSplitRatio; // 默认 100%
}
log.debug("需要重新计算分账比例或当前比例为空,基于店铺分类计算");
// 获取店铺分类信息
ShopBaseStoreCategory category = shopBaseStoreCategoryService.get(storeCategoryId);
if (category != null && category.getSplit_ratio() != null) {
splitRatio = category.getSplit_ratio();
} else {
splitRatio = defaultSplitRatio; // 默认值
}
// 确保比例不超过 100%
if (splitRatio.compareTo(BigDecimal.ZERO) < 0 || splitRatio.compareTo(defaultSplitRatio) > 0) {
splitRatio = defaultSplitRatio;
}
// 通过商户入驻服务计算分账比例传入null作为一级分类店铺分类ID作为二级分类
splitRatio = shopMchEntryService.getMchEntryRatioOrDefault(shopStoreBase.getStore_category_id(), shopStoreBase.getStore_2nd_category_id(), splitRatio, defaultSplitRatio);
log.debug("通过商户入驻服务计算分账比例: {}", splitRatio);
}
// 确保比例在有效范围内 (0%, 100%)
if (splitRatio != null && (splitRatio.compareTo(BigDecimal.ZERO) <= 0 || splitRatio.compareTo(BigDecimal.valueOf(100)) >= 0)) {
log.warn("分账比例超出有效范围 (0, 100),使用默认分账比例: {},当前比例: {}", defaultSplitRatio, splitRatio);
splitRatio = defaultSplitRatio;
}
log.info("获取店铺分账比例完成storeId={}, splitRatio={}", storeId, splitRatio);
return splitRatio;
}
/**
* 修改店铺的营业状态
*
@ -4032,6 +4099,48 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
}
}
/**
* 更新店铺分账比例
*
* @param storeId 店铺ID
* @param splitRatio 分账比例
* @return 更新结果成功返回true失败返回false
*/
protected Boolean updateStoreBaseSplitRatio(Integer storeId, BigDecimal splitRatio) {
log.debug("开始更新店铺分账比例storeId={}, splitRatio={}", storeId, splitRatio);
// 参数校验
if (storeId == null || storeId <= 0) {
log.warn("更新店铺分账比例参数校验失败storeId不能为空或小于等于0");
return false;
}
if (splitRatio == null || splitRatio.compareTo(BigDecimal.ZERO) <= 0
|| splitRatio.compareTo(BigDecimal.valueOf(100)) >= 0) {
log.warn("更新店铺分账比例参数校验失败splitRatio 值无效");
return false;
}
try {
UpdateWrapper<ShopStoreBase> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("store_id", storeId);
updateWrapper.set("split_ratio", splitRatio);
boolean result = update(updateWrapper);
if (result) {
log.info("店铺分账比例更新成功storeId={}, splitRatio={}", storeId, splitRatio);
} else {
log.warn("店铺分账比例更新失败storeId={}, splitRatio={}", storeId, splitRatio);
}
return result;
} catch (Exception e) {
log.error("更新店铺分账比例时发生异常storeId={}, splitRatio={}", storeId, splitRatio, e);
return false;
}
}
// @Override
// public Page<ShopStoreBase> getMobileStoreList(Integer page, Integer rows) {
// QueryWrapper<ShopStoreBase> queryWrapper=new QueryWrapper<>();

View File

@ -1,6 +1,7 @@
package com.suisung.mall.shop.store.service.impl;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.api.ResultCode;
import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.modules.store.ShopStoreCompany;
@ -30,6 +31,19 @@ public class ShopStoreCompanyServiceImpl extends BaseServiceImpl<ShopStoreCompan
@Autowired
private ShopStoreEmployeeService shopStoreEmployeeService;
/**
* 获取店铺公司信息
*
* @param storeId
* @return
*/
@Override
public ShopStoreCompany getCompany(Integer storeId) {
QueryWrapper<ShopStoreCompany> queryWrapper = new QueryWrapper();
queryWrapper.eq("store_id", storeId).orderByAsc("company_id");
return findOne(queryWrapper);
}
@Override
public boolean saveOrUpdateCompany(ShopStoreCompany shopStoreCompany) {

View File

@ -243,6 +243,33 @@ public class ShopStoreEmployeeServiceImpl extends BaseServiceImpl<ShopStoreEmplo
return shopStoreEmployeeMapper.selectByStoreIdRightGroupId(storeId, rightsGroupId);
}
/**
* 根据店铺Id和员工Id获取员工信息
*
* @param storeId
* @param userId
* @param isAdmin
* @return
*/
@Override
public List<ShopStoreEmployee> selectEmployeeByCondition(Integer storeId, Integer userId, Boolean isAdmin) {
if (CheckUtil.isEmpty(storeId)) {
return null;
}
QueryWrapper<ShopStoreEmployee> queryWrapper = new QueryWrapper();
queryWrapper.eq("store_id", storeId);
if (CheckUtil.isNotEmpty(userId)) {
queryWrapper.eq("user_id", userId);
}
if (isAdmin != null) {
queryWrapper.eq("employee_is_admin", isAdmin);
}
return list(queryWrapper);
}
/**
* 根据店铺Id获取店铺的所有员工的个推 CID 列表
*

View File

@ -6,10 +6,11 @@ import java.math.RoundingMode;
public class ProductPriceCalculator {
/**
* 计算切割后的商品单价和数量
*
* @param unitPricePerKg 千克单价/千克
* @param totalWeightKg 总重量千克
* @param pieceWeight 切割单位重量数值
* @param weightUnit 重量单位"g"="kg"=千克
* @param totalWeightKg 总重量千克
* @param pieceWeight 切割单位重量数值
* @param weightUnit 重量单位"g"="kg"=千克
* @return 包含两个元素的数组[切割后单价/单位, 完整单位数量]
*/
public static BigDecimal[] calculatePriceAndQuantity(
@ -70,7 +71,7 @@ public class ProductPriceCalculator {
// 千克单位示例
BigDecimal[] result3 = calculatePriceAndQuantity(
new BigDecimal("100"),
new BigDecimal(100),
new BigDecimal("2.5"),
new BigDecimal("0.5"),
"kg");

View File

@ -57,7 +57,7 @@ public class ShopBatchSubmitListener extends AnalysisEventListener<SxGoosModelEx
this.syncThirdDataService = syncThirdDataService;
this.syncStoreSpecsService = syncStoreSpecsService;
// 创建线程池根据CPU核心数优化
// int corePoolSize = Runtime.getRuntime().availableProcessors();
// int corePoolSize = Runtime.getRuntime().availableProcessors();
// log.info("核心线程数量{}", corePoolSize);
this.executorService = Executors.newFixedThreadPool(6);
this.futures = new ArrayList<>();
@ -74,7 +74,7 @@ public class ShopBatchSubmitListener extends AnalysisEventListener<SxGoosModelEx
sxGoosModelExcel.setProduct_barcode("1");
sxGoosModelExcel.setProduct_name("产品1");
sxGoosModelExcel.setShop_specs("颜色");
sxGoosModelExcel.setRetail_price(new BigDecimal("100"));
sxGoosModelExcel.setRetail_price(new BigDecimal(100));
sxGoosModelExcel.setStock(new BigDecimal("10"));
sxGoosModelExcel.setJsonSpecs("[颜色:红色][尺寸:10]");

View File

@ -121,4 +121,5 @@ job:
- "UpdatePayCardStateJob"
- "RetryMqMsgJob"
- "ProductItemAutoFillJob"
- "XcxSubSendMessageJob"
- "XcxSubSendMessageJob"
- "UpdateOrderSeparateJob"

View File

@ -168,7 +168,7 @@ lakala:
client_id: lsycs
client_secret: XPa1HB5d55Ig0qV8
user_no: 29153396
split_lowest_ratio: 94.00
split_lowest_ratio: 20.00
activity_id: 687
wx_fee: 0.6 # 微信手续费 6/1000
api_pub_key_path: payKey/lakala/dev/tk_api_public_key.txt

View File

@ -147,34 +147,34 @@ sf-express:
#拉卡拉进件配置
lakala:
#服务地址
server_url: https://test.wsmsd.cn/sit
server_url: https://s2.lakala.com
#应用Id
app_id: OP00000003
app_id: OP10000439
#商户证书序列号
serial_no: 00dfba8194c41b84cf
serial_no: 1737359895636
#商户证书
api_cert_path: payKey/lakala/dev/OP00000003_cert.cer
api_cert_path: payKey/lakala/prod/api_cert.cer
#商户私钥
api_pri_key_path: payKey/lakala/dev/OP00000003_private_key.pem
api_pri_key_path: payKey/lakala/prod/api_private_key.pem
#拉卡拉平台证书
lkl_platform_cer_path: payKey/lakala/dev/lkl_notify_cert_v2.cer
lkl_platform_cer_path: payKey/lakala/prod/lkl_platform.cer
#机构代码
org_code: 1951582
is_prod: false
# 拉卡拉拓客进件配置
org_code: 980688
is_prod: true
tk:
#服务地址
server_url: https://test.wsmsd.cn
client_id: lsycs
client_secret: XPa1HB5d55Ig0qV8
user_no: 29153396
split_lowest_ratio: 94.00
activity_id: 687
wx_fee: 0.6 # 微信手续费 6/1000
api_pub_key_path: payKey/lakala/dev/tk_api_public_key.txt
api_pri_key_path: payKey/lakala/dev/tk_api_private_key.txt
notify_pub_key_path: payKey/lakala/dev/tk_notify_public_key.txt
notify_pri_key_path: payKey/lakala/dev/tk_notify_private_key.txt
server_url: https://tkapi.lakala.com
client_id: gpff
client_secret: uRjvaJkWS1A0VsAv
user_no: 22874827
split_lowest_ratio: 20.00
activity_id: 208
wx_fee: 0.25 # 微信手续费 千分之2.5
api_pub_key_path: payKey/lakala/prod/tk_api_public_key.txt
api_pri_key_path: payKey/lakala/prod/tk_api_private_key.txt
notify_pub_key_path: payKey/lakala/prod/tk_notify_public_key.txt
notify_pri_key_path: payKey/lakala/prod/tk_notify_private_key.txt
#e签宝配置
esign:
server_url: https://smlopenapi.esign.cn

View File

@ -183,7 +183,7 @@ lakala:
client_id: gpff
client_secret: uRjvaJkWS1A0VsAv
user_no: 22874827
split_lowest_ratio: 94.00
split_lowest_ratio: 20.00
activity_id: 208
wx_fee: 0.25 # 微信手续费 千分之2.5
api_pub_key_path: payKey/lakala/prod/tk_api_public_key.txt

View File

@ -172,7 +172,7 @@ lakala:
client_id: lsycs
client_secret: XPa1HB5d55Ig0qV8
user_no: 29153396
split_lowest_ratio: 94.00
split_lowest_ratio: 20.00
activity_id: 687
wx_fee: 0.6 # 微信手续费 6/1000
api_pub_key_path: payKey/lakala/dev/tk_api_public_key.txt

View File

@ -172,7 +172,7 @@ lakala:
client_id: lsycs
client_secret: XPa1HB5d55Ig0qV8
user_no: 29153396
split_lowest_ratio: 94.00
split_lowest_ratio: 20.00
activity_id: 687
wx_fee: 0.6 # 微信手续费 6/1000
api_pub_key_path: payKey/lakala/dev/tk_api_public_key.txt

View File

@ -17,15 +17,15 @@
JOIN lkl_area c on c.area_code=b.parent_code and c.type=b.type
<where>
<if test="bankNo!=null and bankNo!=''">
and bank_no = #{bankNo}
and a.bank_no = #{bankNo}
</if>
<if test="branchBankNo!=null and branchBankNo!=''">
and branch_bank_no = #{branchBankNo}
and a.branch_bank_no = #{branchBankNo}
</if>
<if test="branchBankNo!=null and branchBankNo!=''">
and branch_bank_no = #{branchBankNo}
<if test="clearNo!=null and clearNo!=''">
and a.clear_no = #{clearNo}
</if>
<choose>
@ -40,7 +40,7 @@
<if test="keywords!=null and keywords.size>0">
AND (
<foreach collection="keywords" item="keyword" separator="AND">
branch_bank_name LIKE CONCAT('%', #{keyword}, '%')
a.branch_bank_name LIKE CONCAT('%', #{keyword}, '%')
</foreach>
)
</if>

View File

@ -5,7 +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, shop_parent_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_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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,368 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>小发同城电子合同签署</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
line-height: 1.6;
color: #333;
font-size: 16px;
}
.container {
max-width: 100%;
margin: 0 auto;
background-color: #fff;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: linear-gradient(135deg, #4CAF50, #2E7D32);
color: white;
padding: 30px 20px 20px;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.header h1 {
margin: 0;
font-size: 22px;
font-weight: 500;
letter-spacing: 1px;
}
.content {
flex: 1;
padding: 30px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.success-icon {
width: 80px;
height: 80px;
background-color: #e8f5e9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 25px;
}
.success-icon span {
font-size: 40px;
color: #4CAF50;
}
.message {
text-align: center;
margin-bottom: 30px;
width: 100%;
}
.message p {
margin-bottom: 20px;
font-size: 16px;
color: #555;
}
.message p:first-child {
font-size: 18px;
color: #333;
font-weight: 500;
}
.link-container {
background-color: #f0f9ff;
border: 1px dashed #2196F3;
border-radius: 12px;
padding: 20px 15px;
margin: 25px 0;
width: 100%;
text-align: center;
}
.link-title {
font-weight: 500;
color: #1976D2;
margin-bottom: 12px;
font-size: 16px;
}
.link {
color: #1976D2;
word-break: break-all;
font-size: 15px;
font-family: monospace;
padding: 10px;
background-color: #e3f2fd;
border-radius: 8px;
margin-top: 5px;
}
.app-option {
background-color: #fff3e0;
border: 1px solid #ffcc80;
border-radius: 12px;
padding: 20px 15px;
margin: 20px 0;
text-align: center;
}
.app-option p {
margin: 0;
font-size: 16px;
color: #e65100;
}
.app-icon {
font-size: 36px;
margin-bottom: 10px;
}
.warning {
background-color: #fff8e1;
border-left: 4px solid #ffc107;
padding: 15px;
margin: 25px 0;
text-align: left;
border-radius: 0 6px 6px 0;
font-size: 15px;
}
.warning strong {
color: #e65100;
}
.footer {
background-color: #fafafa;
padding: 20px 15px;
text-align: center;
color: #666;
font-size: 14px;
border-top: 1px solid #eee;
}
.contact {
color: #1976D2;
text-decoration: none;
font-weight: 500;
}
.contact:hover, .contact:active {
text-decoration: underline;
}
.btn {
display: block;
width: 100%;
padding: 16px;
background: linear-gradient(135deg, #2196F3, #1976D2);
color: white;
text-align: center;
border: none;
border-radius: 10px;
font-size: 17px;
font-weight: 500;
margin: 20px 0;
cursor: pointer;
box-shadow: 0 4px 6px rgba(33, 150, 243, 0.2);
}
.btn:active {
transform: translateY(1px);
box-shadow: 0 2px 3px rgba(33, 150, 243, 0.2);
}
.copy-btn {
background: #e3f2fd;
color: #1976D2;
margin-top: 10px;
}
/* 移动端优化 */
@media (max-width: 480px) {
.header {
padding: 25px 15px 15px;
}
.header h1 {
font-size: 20px;
}
.content {
padding: 20px 15px;
}
.success-icon {
width: 70px;
height: 70px;
}
.success-icon span {
font-size: 35px;
}
.message p {
font-size: 15px;
}
.message p:first-child {
font-size: 17px;
}
.link-container, .app-option {
padding: 15px 12px;
}
.link {
font-size: 14px;
}
}
/* 超小屏幕优化 */
@media (max-width: 360px) {
body {
font-size: 15px;
}
.header h1 {
font-size: 18px;
}
.btn {
padding: 14px;
font-size: 16px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>小发同城电子合同签署</h1>
</div>
<div class="content">
<div class="success-icon">
<span></span>
</div>
<div class="message">
<p>恭喜!您的开店入驻申请已审核通过</p>
<p>请在24小时内完成电子合同签署</p>
<div class="link-container">
<div class="link-title">合作方签署链接24小时内有效</div>
<div class="link" id="signLink" th:text="${resultUrl}">${resultUrl}</div>
<button class="btn copy-btn" onclick="copyLink()">复制链接</button>
</div>
<!-- <div class="app-option">-->
<!-- <div class="app-icon">📱</div>-->
<!-- <p>或前往<strong>小发商家版APP</strong>完成签署</p>-->
<!-- </div>-->
<button class="btn" onclick="openLink()">立即签署合同</button>
<div class="warning">
<strong>注意:</strong>链接有效期为24小时逾期需重新提交申请。
</div>
</div>
</div>
<div class="footer">
<p>如有疑问请联系客服:<a class="contact" href="tel:17777525395">17777525395</a></p>
<p>感谢您对小发同城的支持!</p>
</div>
</div>
<script th:inline="javascript">
// 获取后端传递的resultUrl变量
var resultUrl = '${resultUrl}';
// 复制链接功能
function copyLink() {
var linkText = document.getElementById('signLink').textContent;
if (navigator.clipboard) {
navigator.clipboard.writeText(linkText).then(() => {
alert('链接已复制到剪贴板');
}).catch(err => {
console.error('复制失败:', err);
fallbackCopyTextToClipboard(linkText);
});
} else {
fallbackCopyTextToClipboard(linkText);
}
}
// 兼容性复制方法
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
alert('链接已复制到剪贴板');
} else {
prompt("请手动复制以下链接:", text);
}
} catch (err) {
prompt("请手动复制以下链接:", text);
}
document.body.removeChild(textArea);
}
// 打开链接功能
function openLink() {
if (resultUrl) {
// 如果是完整URL则直接跳转
if (resultUrl.startsWith('http') || resultUrl.startsWith('https')) {
window.location.href = resultUrl;
} else {
// 否则添加协议前缀
window.location.href = 'https://' + resultUrl;
}
} else {
alert('链接无效,请联系客服');
}
}
// 检测是否在微信中打开
var ua = navigator.userAgent.toLowerCase();
if (ua.includes('micromessenger')) {
// 在微信中显示提示
var btn = document.querySelector('.btn');
if (btn) {
btn.textContent = '点击右上角菜单选择在浏览器中打开';
}
}
// 防止页面被嵌入到iframe中
if (window.self !== window.top) {
window.top.location = window.location;
}
</script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More