账号密码 fix bug

This commit is contained in:
Jack 2025-08-05 20:23:54 +08:00
parent 99a88a86a9
commit ee653ba1c0
27 changed files with 652 additions and 228 deletions

View File

@ -7,12 +7,14 @@ import cn.hutool.json.JSONObject;
import com.suisung.mall.account.service.AccountUserBaseService;
import com.suisung.mall.account.service.AccountUserInfoService;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.api.ResultCode;
import com.suisung.mall.common.constant.AuthConstant;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.constant.RedisConstant;
import com.suisung.mall.common.pojo.req.WxUserInfoReq;
import com.suisung.mall.common.service.GeTuiPushService;
import com.suisung.mall.common.service.impl.BaseControllerImpl;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.common.utils.UserInfoService;
@ -253,6 +255,11 @@ public class LoginController extends BaseControllerImpl {
return CommonResult.failed("缺少必要参数!");
}
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(userMobile, randKey, verifyCode)) {
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
String cid = paramJSON.getStr("cid");
String osType = paramJSON.getStr("osType");

View File

@ -87,10 +87,19 @@ public interface AccountUserBaseService extends IBaseService<AccountUserBase> {
* @param user_is_admin
* @return
*/
AccountUserBase getByAccount(String user_account, Integer user_is_admin);
AccountUserBase getByAccountAndType(String user_account, Integer user_is_admin);
/**
* 根据账号获取一条记录
* 根据账号获取一条普通账号记录
*
* @param user_account
* @return
*/
AccountUserBase getNormalByAccount(String user_account);
/**
* 根据账号获取一条账号信息(不限于管理员和普通会员)
*
* @param user_account
* @return

View File

@ -54,6 +54,7 @@ import com.suisung.mall.common.pojo.dto.SmsDto;
import com.suisung.mall.common.pojo.req.WxUserInfoReq;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
import com.suisung.mall.common.service.MessageService;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.*;
import com.suisung.mall.common.utils.constbank.RSAUtil;
import com.suisung.mall.common.utils.phone.PhoneNumberUtils;
@ -1195,6 +1196,11 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
@Override
public CommonResult editUser() {
UserDto user = getCurrentUser();
if (user == null) {
throw new ApiUserException(_("用户信息异常!"));
}
Integer user_id = Convert.toInt(getParameter("user_id"));
if (user_id == null) {
return addUser();
@ -1230,10 +1236,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
data.put("user_mobile", user_mobile); // 手机号码(mobile)
data.put("user_intl", user_intl);
UserDto user = getCurrentUser();
if (user == null) {
throw new ApiUserException(_("用户信息异常!"));
}
Integer admin_user_id = user.getId();
if (ObjectUtil.equal(admin_user_id, user_id) && CheckUtil.isEmpty(user_state)) {
throw new ApiException(_("管理员不可以关闭当前账号!"));
@ -1677,6 +1680,11 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
throw new ApiException(_("请输入账号"));
}
// 检查输入信息是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(user_account, user_mobile, rand_key, verifyCode, user_email)) {
throw new ApiException(ResultCode.VALIDATE_INPUTS);
}
logger.debug("### 注册参数:{}###", JSONUtil.toJsonStr(userInfo));
// todo 支持国外手机号 isMobile() 方法
@ -1711,7 +1719,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
bind_type = BindCode.MOBILE;
}
// 是否为账号的注册(主要微信授权openid)
// 是否为账号的注册(主要微信授权openid 后台平台新增店铺管理员)
if (StrUtil.isNotBlank(user_account) && StrUtil.isNotBlank(verifyCode)
&& StrUtil.isNotBlank(verify_token) && ObjectUtil.isNull(userInfo.get("user_email"))) {
@ -1767,7 +1775,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
verifyPwd(user_password); // 忽略密码校验
AccountUserBase user_base_row = getByAccount(user_account, userIsAdmin);
AccountUserBase user_base_row = getByAccountAndType(user_account, userIsAdmin);
if (user_base_row != null) {
// 检测到 account_user_base 用户已经存在是因为账号没有绑定这时绑定账号即可
// 绑定基本关系
@ -1971,6 +1979,8 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
*/
@Override
public boolean doResetPasswd(String user_account, String user_password, String old_password) {
// logger.info("重置账号密码:{},{},{}", user_account, user_password, old_password);
if (StrUtil.isBlank(user_account)) {
throw new ApiException(_("缺少账号信息"));
}
@ -1979,6 +1989,12 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
throw new ApiException(_("请输入新密码"));
}
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(user_account, user_password, old_password)) {
new ApiException(ResultCode.VALIDATE_INPUTS);
// return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
verifyPwd(user_password); // 密码格式策略验证
// 检测登录状态
@ -2008,6 +2024,8 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
if (!saveOrUpdate(reset_passwd_row)) {
throw new ApiException(ResultCode.FAILED);
}
// logger.info("重置账号密码完成");
}
return true;
@ -2255,6 +2273,11 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
String bind_id = user_intl + mobile;
Integer user_id;
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(user_intl, mobile)) {
new ApiException(ResultCode.VALIDATE_INPUTS);
}
// 判断是否已经绑定
QueryWrapper<AccountUserBindConnect> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("bind_id", bind_id).eq("bind_type", BindCode.MOBILE);
@ -2328,6 +2351,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
return user_id;
}
/**
* 根据账号和账号类型获取一条记录
*
@ -2335,8 +2359,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
* @param user_is_admin
* @return
*/
@Override
public AccountUserBase getByAccount(String user_account, Integer user_is_admin) {
public AccountUserBase getByAccountAndType(String user_account, Integer user_is_admin) {
QueryWrapper<AccountUserBase> queryWrapper = new QueryWrapper<>();
if (user_is_admin == null) {
user_is_admin = CommonConstant.USER_TYPE_NORMAL;
@ -2349,8 +2372,21 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
}
@Override
public AccountUserBase getNormalByAccount(String user_account) {
return getByAccountAndType(user_account, CommonConstant.USER_TYPE_NORMAL);
}
/**
* 根据账号获取一条账号信息(不限于管理员和普通会员)
*
* @param user_account
* @return
*/
public AccountUserBase getByAccount(String user_account) {
return getByAccount(user_account, CommonConstant.USER_TYPE_NORMAL);
QueryWrapper<AccountUserBase> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_account", user_account)
.orderByAsc("user_id");
return findOne(queryWrapper);
}
/**
@ -2482,7 +2518,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
} else {
// number 是userAccount账号格式
AccountUserBase accountUserBase = accountUserBaseService.getByAccount(number);
AccountUserBase accountUserBase = accountUserBaseService.getNormalByAccount(number);
if (accountUserBase != null) {
accountUserBindConnect = accountUserBindConnectService.getBindByUserId(accountUserBase.getUser_id(), BindCode.MOBILE, accountUserBase.getUser_is_admin());
if (accountUserBindConnect != null) {
@ -2893,6 +2929,12 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
return CommonResult.failed(_("缺少 OpenId"));
}
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(wxUserInfoReq.getPhoneNumber(), wxUserInfoReq.getOpenId())) {
// new ApiException(ResultCode.VALIDATE_INPUTS);
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
String iddCode = wxUserInfoReq.getCountryCode();
if (StrUtil.isBlank(iddCode) || !PhoneNumberUtils.isValidCountryCode(iddCode)) {
iddCode = CommonConstant.IDD_ZH_CN;
@ -2901,7 +2943,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
// 带国家编码的手机号
String mobile = PhoneNumberUtils.convWithIDDCodePhoneNumber(wxUserInfoReq.getPhoneNumber(), iddCode);
AccountUserBase accountUserBase = getByAccount(mobile, CommonConstant.USER_TYPE_NORMAL);
AccountUserBase accountUserBase = getByAccountAndType(mobile, CommonConstant.USER_TYPE_NORMAL);
if (accountUserBase == null) {
// 检测到用户尚未注册立即新增用户基本信息和用户附加信息
Date today = new Date();
@ -3054,6 +3096,12 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
public CommonResult doMobileBindLogin(String user_mobile, String password) {
AccountUserBindConnect bind_row = bindConnectService.get(user_mobile);
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(user_mobile, password)) {
// new ApiException(ResultCode.VALIDATE_INPUTS);
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
if (bind_row != null && ObjectUtil.equal(BindCode.MOBILE, bind_row.getBind_type())) {
// AccountUserBindConnect accountUserBindConnect = accountUserBindConnectService.getBindByBindId(user_mobile, BindCode.MOBILE, CommonConstant.USER_TYPE_NORMAL);
// if (accountUserBindConnect != null) {
@ -3093,6 +3141,12 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
return CommonResult.failed("缺少必要参数!");
}
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(user_mobile, cid, osType)) {
// new ApiException(ResultCode.VALIDATE_INPUTS);
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
// 查询绑定手机的商家账号
AccountUserBindConnect bind_row = accountUserBindConnectService.getBindByBindId(user_mobile, BindCode.MOBILE, userType);
AccountUserBase accountUserBase;
@ -3151,6 +3205,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
Integer user_id = null;
Integer user_type = CommonConstant.USER_TYPE_NORMAL;
switch (bind_name) {
case "weixin":
id_prefix = "wxopenapp";
@ -3488,6 +3543,12 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
// return CommonResult.failed("请先登录再试!");
// }
// 检查输入字符是不是包含 sql 注入特征如果包含不给以通过
if (!CommonService.isValidInput(userAccountOrMobile, verifyCode, newPassword)) {
// new ApiException(ResultCode.VALIDATE_INPUTS);
return CommonResult.failed(ResultCode.VALIDATE_INPUTS);
}
AccountUserBase accountUserBase;
String mobileOrEmail = "";
boolean isMobile = PhoneNumberUtils.checkPhoneNumber(userAccountOrMobile);
@ -3507,7 +3568,7 @@ public class AccountUserBaseServiceImpl extends BaseServiceImpl<AccountUserBaseM
mobileOrEmail = PhoneNumberUtils.convZhPhoneNumber(accountUserBindConnect.getBind_id());
} else {
// number 是userAccount账号格式
accountUserBase = accountUserBaseService.getByAccount(userAccountOrMobile);
accountUserBase = accountUserBaseService.getNormalByAccount(userAccountOrMobile);
if (accountUserBase == null) {
return CommonResult.failed(_("账号有异常!"));
}

View File

@ -245,7 +245,7 @@ public class AccountUserBindConnectServiceImpl extends BaseServiceImpl<AccountUs
String bind_id = accountUserBindConnect.getBind_id(); // 获取绑定ID如微信openid
// 1. 首先尝试通过绑定ID查找已有用户
AccountUserBase accountUserBase = accountUserBaseService.getByAccount(bind_id);
AccountUserBase accountUserBase = accountUserBaseService.getNormalByAccount(bind_id);
// 2. 处理unionId绑定逻辑微信生态专用
if (StrUtil.isNotBlank(accountUserBindConnect.getBind_unionid()) && ObjectUtil.isNull(accountUserBase)) {

View File

@ -7,6 +7,7 @@ import com.suisung.mall.account.mapper.UserDeviceBindMapper;
import com.suisung.mall.account.service.AccountUserBindGeTuiService;
import com.suisung.mall.common.constant.CommonConstant;
import com.suisung.mall.common.modules.account.AccountUserBindGeTui;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -29,21 +30,23 @@ public class AccountUserBindGeTuiServiceImpl extends BaseServiceImpl<UserDeviceB
// 检查参数是否有效
if (ObjectUtil.isNull(accountUserBindGeTui) ||
StrUtil.isBlank(accountUserBindGeTui.getCid()) ||
ObjectUtil.isNull(accountUserBindGeTui.getUserId())) {
log.error("缺少必要参数cid={}, userId={}",
CheckUtil.isEmpty(accountUserBindGeTui.getUserId())
|| CheckUtil.isEmpty(accountUserBindGeTui.getUserType())) {
log.error("缺少必要参数cid={}, userId={}, userType={}",
accountUserBindGeTui != null ? accountUserBindGeTui.getCid() : null,
accountUserBindGeTui != null ? accountUserBindGeTui.getUserId() : null);
accountUserBindGeTui != null ? accountUserBindGeTui.getUserId() : null,
accountUserBindGeTui != null ? accountUserBindGeTui.getUserType() : null);
return false;
}
// 获取操作系统类型默认为1 手机系统类型 1-Android2-iOS3-微信小程序
Integer osType = accountUserBindGeTui.getOsType() != null ? accountUserBindGeTui.getOsType() : 1;
String cid = accountUserBindGeTui.getCid();
// 构建查询条件
// 构建查询条件 组合健用户IDCID用户类型手机系统类型
QueryWrapper<AccountUserBindGeTui> wrapper = new QueryWrapper<AccountUserBindGeTui>()
.eq("user_id", accountUserBindGeTui.getUserId())
.eq("os_type", osType)
.eq("user_type", accountUserBindGeTui.getUserType())
.eq("status", CommonConstant.Enable)
.orderByDesc("id");

View File

@ -9,6 +9,7 @@ public enum ResultCode implements IErrorCode {
SUCCESS(0, 200, "操作成功!", "success"),
FAILED(0, 250, "操作失败!", "failed"),
VALIDATE_FAILED(0, 250, "参数检验失败!", "failed"),
VALIDATE_INPUTS(0, 250, "参数不符合规范!", "failed"),
UNAUTHORIZED(30, 250, "暂未登录或token已经过期", "failed"),
FORBIDDEN(0, 250, "没有相关权限!", "failed"),
NOT_IS_PLATFORM(0, 250, "非平台管理员账号,没有相关权限!", "failed"),
@ -37,29 +38,29 @@ public enum ResultCode implements IErrorCode {
I18nUtil._("远程调用异常!")
I18nUtil._("未设置支付密码!")
*/
private long code;
private final long code;
private long status;
private String message;
private final String message;
private String msg;
private ResultCode(long code, String message) {
ResultCode(long code, String message) {
this.code = code;
this.message = message;
}
private ResultCode(long code, long status, String message) {
ResultCode(long code, long status, String message) {
this.code = code;
this.status = status;
this.message = message;
}
private ResultCode(long code, String message, String msg) {
ResultCode(long code, String message, String msg) {
this.code = code;
this.message = message;
this.msg = msg;
}
private ResultCode(long code, long status, String message, String msg) {
ResultCode(long code, long status, String message, String msg) {
this.code = code;
this.status = status;
this.message = message;

View File

@ -76,11 +76,12 @@ public class CommonConstant {
* * mchRetrunOrderList-商家退款订单列表
*/
public static final String PUSH_MSG_CATE_EC = "mchContract";
public static final String PUSH_MSG_CATE_CREATE_SF_SHOP = "createSFExpress";
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 CONF_KEY_SAME_CITY_ORDER_EXPIRE_SECONDS = "sameCityOrderExpireSeconds";
}

View File

@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
@ -85,5 +86,11 @@ public class AccountUserBase implements Serializable {
@ApiModelProperty(value = "rid")
private Integer rid;
@ApiModelProperty(value = "新建时间")
private Date created_at;
@ApiModelProperty(value = "更新时间")
private Date updated_at;
}

View File

@ -1,34 +0,0 @@
/*
* 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.common.service;
/**
* 通用服务接口
*/
public interface CommonService {
/**
* 尝试获取分布式锁
*
* @param lockKey 锁的key
* @param expireSeconds 锁过期时间
* @return 锁标识解锁时需用加锁失败返回null
*/
String tryDistributedLock(String lockKey, long expireSeconds);
/**
* 释放分布式锁
*
* @param lockKey 锁的key
* @param lockValue 加锁时返回的value确保只有持有锁的线程能解锁
* @return 是否释放成功
*/
boolean releaseLock(String lockKey, String lockValue);
}

View File

@ -0,0 +1,114 @@
/*
* 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.common.service.impl;
import com.suisung.mall.common.api.StateCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.regex.Pattern;
@Slf4j
@Service
public class CommonService {
// 预编译SQL注入特征正则静态初始化仅加载一次
private static final Pattern[] INJECTION_PATTERNS;
static {
// SQL注入核心特征库
String[] sqlFeatures = {
"\\bselect\\b", "\\binsert\\b", "\\bupdate\\b", "\\bdelete\\b",
"\\bdrop\\b", "\\btruncate\\b", "\\balter\\b", "\\bunion\\b",
"\\bexec\\b", "\\bdeclare\\b", "\\bcall\\b", "\\bcreate\\b",
"\\-\\-", "\\#", "/\\*", "\\*/", "'", "\"",
"\\(\\s*or\\s*\\d+", "\\(\\s*and\\s*\\d+",
"or\\s+\\d+=\\d+", "and\\s+\\d+=\\d+",
"union\\s+select", "union\\+select",
"xp_", "sp_", "db_",
"\\bsleep\\(", "\\bdelay\\(", "\\bwaitfor\\b",
"\\bcast\\(", "\\bconvert\\(", "\\bchar\\("
};
// 初始化正则数组忽略大小写
INJECTION_PATTERNS = new Pattern[sqlFeatures.length];
for (int i = 0; i < sqlFeatures.length; i++) {
INJECTION_PATTERNS[i] = Pattern.compile(sqlFeatures[i], Pattern.CASE_INSENSITIVE);
}
}
/**
* 判断是否为顺丰同城配送
*
* @param deliveryTypeId 配送方式ID
* @return 是否为顺丰同城配送
*/
public static boolean isSFExpress(Integer deliveryTypeId) {
// 当配送方式ID为空时返回false
if (deliveryTypeId == null) {
return false;
}
// 判断是否为顺丰同城配送类型
return deliveryTypeId.equals(StateCode.DELIVERY_TYPE_SAME_CITY);
}
/**
* 判断配送方式是否为普通配送
*
* @param deliveryTypeId 配送方式ID
* @return 是否为普通配送
*/
public static boolean isNormalExpress(Integer deliveryTypeId) {
// 当配送方式ID为空时返回false
if (deliveryTypeId == null) {
return false;
}
// 普通配送是指除了同城配送自提和到店服务之外的配送方式
return !deliveryTypeId.equals(StateCode.DELIVERY_TYPE_SAME_CITY)
&& !deliveryTypeId.equals(StateCode.DELIVERY_TYPE_SELF_PICK_UP)
&& !deliveryTypeId.equals(StateCode.DELIVERY_TYPE_IN_STORE_SERVICE);
}
/**
* 检测账号是否包含SQL注入特征
*
* @param inputs 待检测的输入的字符串可变参数
* @return true-无注入风险false-包含注入特征
*/
public static boolean isValidInput(String... inputs) {
// 如果输入为null认为无风险
if (inputs == null) {
return true;
}
// 检查每个输入参数
for (String input : inputs) {
// 空值或空白字符串直接跳过验证认为空字符串不包含SQL注入特征
if (input == null || input.isEmpty()) {
continue;
}
// 检测是否匹配任何注入特征
for (Pattern pattern : INJECTION_PATTERNS) {
if (pattern.matcher(input).find()) {
return false;
}
}
}
return true;
}
public static void main(String[] args) {
System.out.println(isValidInput("", "+8618924071446"));
}
}

View File

@ -1,86 +0,0 @@
/*
* 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.common.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class CommonServiceImpl {
@Lazy
@Resource
private RedisTemplate redisTemplate;
/**
* 尝试获取分布式锁
* <p>
* 使用实例
* String lockKey = "order:123";
* long expireSeconds = 10;
* String lockValue = commonServiceImpl.tryDistributedLock(lockKey, expireSeconds);
* if (lockValue != null) {
* try {
* // 执行业务逻辑
* } finally {
* commonServiceImpl.releaseLock(lockKey, lockValue);
* }
* } else {
* // 获取锁失败做相应处理
* }
*
* @param lockKey 锁的key
* @param expireSeconds 锁过期时间
* @return 锁标识解锁时需用加锁失败返回null
*/
public String tryDistributedLock(String lockKey, long expireSeconds) {
String lockValue = UUID.randomUUID().toString();
try {
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success) ? lockValue : null;
} catch (Exception e) {
// 记录异常日志实际项目可用Logger
e.printStackTrace();
return null;
}
}
/**
* 释放分布式锁
*
* @param lockKey 锁的key
* @param lockValue 加锁时返回的value确保只有持有锁的线程能解锁
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
try {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = (Long) redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
return result != null && result > 0;
} catch (Exception e) {
log.error("释放分布式锁异常key={}, error={}", lockKey, e.getMessage(), e);
return false;
}
}
}

View File

@ -65,11 +65,6 @@ public class LakalaApiServiceImpl implements LakalaApiService {
private static final boolean init = false;
private static final String lklSuccessCode = "000000";
private static final String lklSacsSuccessCode = "SACS0000";
// 可选的两个参数不同的店铺商家可以数据库里配置不同的商户号和终端号
// @Value("${lakala.merchant_no}")
// public String merchantNo; // 拉卡拉分配的商户号
// @Value("${lakala.term_no}")
// public String termNo; // 拉卡拉分配的终端号码
@Value("${lakala.server_url}")
private String serverUrl; //服务地址
@ -104,12 +99,15 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Autowired
private ShopService shopService;
@Lazy
@Autowired
private LklLedgerMemberService lklLedgerMemberService;
@Lazy
@Autowired
private LklLedgerReceiverService lklLedgerReceiverService;
@Lazy
@Autowired
private LklLedgerMerReceiverBindService lklLedgerMerReceiverBindService;
@ -125,16 +123,6 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Resource
private ShopMessageTemplateService shopMessageTemplateService;
// @Lazy
// @Resource
// private
// EsignContractService esignContractService;
//
// @Lazy
// @Resource
// private EsignContractFillingFileService esignContractFillingFileService;
@Lazy
@Resource
private ShopStoreBaseService shopStoreBaseService;
@ -155,6 +143,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Resource
private PushMessageService pushMessageService;
@Lazy
@Resource
private OssService ossService;
@ -533,7 +522,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
JSONObject respData = respBody.getJSONObject("resp_data");
if (respData == null) {
errMsg = "申请入网电子合同失败,返回数据有误";
errMsg = "申请入网电子合同失败,无data返回数据";
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), "", CommonConstant.MCH_APPR_STA_LKL_NOPASS, errMsg);
return Pair.of(false, errMsg);
}
@ -789,13 +778,12 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 更新商家入驻表的合同编号和签署地址更改状态
shopMchEntryService.updateMerchantLklElectronicContractInfo(lklLedgerEc.getMch_id(), ecNo, paramsJSON.getStr("ecName"), lklLedgerEc.getResult_url(), ecCosFileUrl, eclklFilePath);
// TODO 商家电子合同签署完毕后收到异步通知触发拉卡拉商家进件重要环节
// 下一步等待拉卡拉系统审核和人工审核收到异步通知之后触发1e签宝的电子合同签署2新增分账接收方
// 商家电子合同签署完毕后收到异步通知触发拉卡拉商家进件重要环节
Pair<Boolean, String> resultPair = lklTkService.registrationMerchant(lklLedgerEc.getMch_mobile(), "");
if (!resultPair.getFirst()) {
errMsg += resultPair.getSecond();
log.error(errMsg);
errMsg = resultPair.getSecond();
shopMchEntryService.updateMerchEntryApprovalByMchId(lklLedgerEc.getMch_id(), "", CommonConstant.MCH_APPR_STA_LKL_NOPASS, errMsg);
log.error(errMsg);
throw new ApiException(errMsg);
}

View File

@ -504,7 +504,6 @@ public class LklTkServiceImpl {
urlPath = "/registration/merchant";
}
// String urlPath = isProd() ? "/registration/merchant" : "/sit/htkregistration/merchant";
try {
logger.info("进件请求参数:{}", JSONUtil.toJsonStr(formData));
@ -541,6 +540,7 @@ public class LklTkServiceImpl {
}
shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), "", CommonConstant.MCH_APPR_STA_LKL_NOPASS, "商户进件:提交进件成功,请等待审核!");
return Pair.of(true, "提交进件成功,请等待审核!");
}

View File

@ -59,12 +59,14 @@ import com.suisung.mall.common.modules.user.*;
import com.suisung.mall.common.pojo.dto.StandardAddressDTO;
import com.suisung.mall.common.pojo.dto.WxOrderBaseInfoDTO;
import com.suisung.mall.common.pojo.req.*;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
import com.suisung.mall.common.pojo.to.MsgTO;
import com.suisung.mall.common.pojo.to.PayMoneyTO;
import com.suisung.mall.common.pojo.to.PayPointTO;
import com.suisung.mall.common.pojo.to.UserLevelTO;
import com.suisung.mall.common.pojo.vo.ShopStoreOrderProductPrintVO;
import com.suisung.mall.common.service.MessageService;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.*;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.activity.service.*;
@ -92,6 +94,7 @@ import com.suisung.mall.shop.order.vo.OrderReturnInputVo;
import com.suisung.mall.shop.order.vo.OrderReturnItemInputVo;
import com.suisung.mall.shop.product.pojo.vo.FixOrderVo;
import com.suisung.mall.shop.product.service.*;
import com.suisung.mall.shop.sfexpress.service.SFExpressApiService;
import com.suisung.mall.shop.store.service.*;
import com.suisung.mall.shop.sync.service.SyncThirdDataService;
import com.suisung.mall.shop.user.service.*;
@ -144,6 +147,7 @@ import static com.suisung.mall.common.utils.ContextUtil.getCurrentUser;
public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMapper, ShopOrderBase> implements ShopOrderBaseService {
private final Logger logger = LoggerFactory.getLogger(ShopOrderBaseServiceImpl.class);
@Lazy
@Autowired
private ShopOrderBaseMapper shopOrderBaseMapper;
@Lazy
@ -152,32 +156,43 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Lazy
@Autowired
private ShopOrderReturnService orderReturnService;
@Lazy
@Autowired
private ShopOrderReturnItemService orderReturnItemService;
@Lazy
@Autowired
private ShopStoreBaseService shopStoreBaseService;
@Lazy
@Autowired
private ShopStoreInfoService shopStoreInfoService;
@Lazy
@Autowired
private ShopOrderItemService shopOrderItemService;
@Lazy
@Autowired
private ShopOrderDataService shopOrderDataService;
@Lazy
@Autowired
private ShopBaseActivityTypeService activityTypeService;
@Lazy
@Autowired
private PayService payService;
@Lazy
@Autowired
private ShopOrderChainCodeService orderChainCodeService;
@Lazy
@Autowired
private ShopChainBaseService shopChainBaseService;
@Lazy
@Autowired
private ShopOrderLogisticsService orderLogisticsService;
@Lazy
@Autowired
private ShopStoreExpressLogisticsService expressLogisticsService;
@Lazy
@Autowired
private ShopOrderDeliveryAddressService orderDeliveryAddressService;
@Lazy
@Autowired
private ShopOrderInvoiceService orderInvoiceService;
@Lazy
@ -189,40 +204,55 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Lazy
@Autowired
private ShopProductItemService shopProductItemService;
@Lazy
@Autowired
private ShopBaseProductUnitService baseProductUnitService;
@Lazy
@Autowired
private ShopBaseStateCodeService shopBaseStateCodeService;
@Lazy
@Autowired
private ShopProductValidPeriodService validPeriodService;
@Lazy
@Autowired
private ShopActivityGroupbookingService activityGroupbookingService;
@Lazy
@Autowired
private ShopActivityGroupbookingHistoryService groupbookingHistoryService;
@Lazy
@Autowired
private ShopOrderStateLogService shopOrderStateLogService;
@Lazy
@Autowired
private InvoicingStockBillService invoicingStockBillService;
@Lazy
@Autowired
private InvoicingStockBillItemService invoicingStockBillItemService;
@Lazy
@Autowired
private ShopStoreConfigService shopStoreConfigService;
@Lazy
@Autowired
private InvoicingWarehouseBaseService warehouseBaseService;
@Lazy
@Autowired
private ShopChainItemService shopChainItemService;
@Lazy
@Autowired
private ShopOrderDeliveryAddressService deliveryAddressService;
@Lazy
@Autowired
private ShopUserDeliveryAddressService userDeliveryAddressService;
@Lazy
@Autowired
private CityMarketOrderBaseService cityMarketOrderBaseService;
@Lazy
@Autowired
private AccountBaseConfigService accountBaseConfigService;
@Lazy
@Autowired
private AccountService accountService;
@Lazy
@Autowired
private ShopUserInvoiceService shopUserInvoiceService;
@Lazy
@ -238,19 +268,25 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Lazy
@Autowired
private ShopUserVoucherService shopUserVoucherService;
@Lazy
@Autowired
private ShopBaseCurrencyService shopBaseCurrencyService;
@Lazy
@Autowired
private ShopOrderCbService shopOrderCbService;
@Lazy
@Autowired
private ShopActivityPfGroupbuyStoreHistoryService activityPfGroupbuyStoreHistoryService;
@Lazy
@Autowired
private ShopActivityCutpriceService activityCutpriceService;
@Lazy
@Autowired
private ShopBaseProductCategoryService shopBaseProductCategoryService;
@Lazy
@Autowired
private InvoicingCustomerBaseService invoicingCustomerBaseService;
@Lazy
@Autowired
private UserInfoService userInfoService;
@Lazy
@ -262,57 +298,76 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Lazy
@Autowired
private ShopActivityGroupbuyStoreService shopActivityGroupbuyStoreService;
@Lazy
@Autowired
private ShopNumberSeqService shopNumberSeqService;
@Lazy
@Autowired
private ShopStoreAnalyticsService shopStoreAnalyticsService;
@Lazy
@Autowired
private ShopProductAnalyticsService shopProductAnalyticsService;
@Lazy
@Autowired
private ShopUserExpHistoryService shopUserExpHistoryService;
@Lazy
@Autowired
private ShopBaseExpressService shopBaseExpressService;
@Lazy
@Autowired
private ShopStoreShippingAddressService shopStoreShippingAddressService;
@Lazy
@Autowired
private ShopOrderShippingAddressService shopOrderShippingAddressService;
@Lazy
@Autowired
private ShopStoreEmployeeService shopStoreEmployeeService;
@Lazy
@Autowired
private ShopDistributionUserOrderService shopDistributionUserOrderService;
@Lazy
@Autowired
private ShopDistributionUserOrderItemService shopDistributionUserOrderItemService;
@Lazy
@Autowired
private ShopDistributionPlantformUserService shopDistributionPlantformUserService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Lazy
@Autowired
private MessageService messageService;
@Lazy
@Autowired
private EduService eduService;
@Lazy
@Autowired
private ShopStoreActivityCodeService shopStoreActivityCodeService;
@Lazy
@Autowired
private MqMessageService mqMessageService;
@Autowired
private ThreadPoolExecutor executor;
@Lazy
@Autowired
private ShopActivityCutpriceService shopActivityCutpriceService;
@Lazy
@Autowired
private ShopOrderDeliveryAddressService shopOrderDeliveryAddressService;
@Lazy
@Autowired
private ShopStoreSameCityTransportBaseService shopStoreSameCityTransportBaseService;
@Lazy
@Autowired
private ShopMessageTemplateService shopMessageTemplateService;
@Lazy
@Autowired
private ShopStoreSfOrderService shopStoreSfOrderService;
@Lazy
@Autowired
private KdApiExpressSearchService kdApiExpressSearchService;
@Lazy
@Autowired
private SFExpressApiService sfExpressApiService;
@Lazy
@Autowired
private LakalaApiService lakalaApiService;
@ -321,6 +376,13 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
@Autowired
private SyncThirdDataService syncThirdDataService;
@Autowired
private ThreadPoolExecutor executor;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Value("${sf-express.enable}")
private Integer enable_sf_express;
@ -8588,13 +8650,41 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
// 处理订单列表数据
pageList.getRecords().forEach(order -> {
// 处理物流轨迹信息仅限快递配送且已发货/已完成订单
if (StateCode.DELIVERY_TYPE_SELF_PICK_UP != order.getDelivery_type_id()
&& StateCode.DELIVERY_TYPE_SAME_CITY != order.getDelivery_type_id()
&& (StateCode.ORDER_STATE_FINISH == order.getOrder_state_id() || StateCode.ORDER_STATE_SHIPPED == order.getOrder_state_id())) {
try {
order.setLogistics_traces(kdApiExpressSearchService.getLogisticsTraces(order.getOrder_id()));
} catch (Exception e) {
logger.error("获取物流轨迹失败订单ID{}", order.getOrder_id(), e);
Integer orderStateId = order.getOrder_state_id();
Integer deliveryTypeId = order.getDelivery_type_id();
if (orderStateId != null && deliveryTypeId != null) {
// 处理普通快递物流轨迹
if (CommonService.isNormalExpress(deliveryTypeId)
&& orderStateId >= StateCode.ORDER_STATE_SHIPPED
&& orderStateId <= StateCode.ORDER_STATE_FINISH) {
try {
order.setLogistics_traces(kdApiExpressSearchService.getLogisticsTraces(order.getOrder_id()));
} catch (Exception e) {
logger.error("获取物流轨迹失败订单ID{}", order.getOrder_id(), e);
}
}
// 处理顺丰同城物流轨迹
else if (CommonService.isSFExpress(deliveryTypeId)
&& orderStateId >= StateCode.ORDER_STATE_SHIPPED
&& orderStateId <= StateCode.ORDER_STATE_FINISH
&& order.getSf_order_info() != null
&& order.getSf_order_info().getFeed() == null) {
// 获取顺丰同城的物流轨迹
Map<String, Object> params = new HashMap<>();
params.put("order_id", order.getSf_order_info().getSf_order_id());
try {
ThirdApiRes feedRes = sfExpressApiService.listOrderFeed(params);
logger.info("获取配送员物流轨迹:{}", feedRes);
if (feedRes != null && feedRes.getError_code() != null && feedRes.getError_code().equals(0)) {
JSONObject result = JSONUtil.parseObj(feedRes.getResult());
if (result != null && result.get("feed") != null) {
order.getSf_order_info().setFeed(JSONUtil.toJsonStr(result.get("feed")));
}
}
} catch (Exception e) {
logger.error("获取顺丰同城物流轨迹失败订单ID{}", order.getOrder_id(), e);
}
}
}
@ -8630,21 +8720,74 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
/**
* 获取商家订单详情
*
* @param orderId
* @param expireSeconds 配送超时的秒数单位
* @return
* @param orderId 订单ID
* @param expireSeconds 配送超时的秒数单位
* @return 商家订单详情
*/
@Override
public MchOrderInfoDTO getMchOrderDetail(String orderId, Long expireSeconds) {
// 检查订单ID是否为空
if (StrUtil.isBlank(orderId)) {
return null;
}
// 设置默认过期时间7天
if (expireSeconds == null || expireSeconds <= 0) {
expireSeconds = 60 * 60 * 24 * 7 * 1000L;
}
return shopOrderBaseMapper.getMchOrderDetail(orderId, expireSeconds);
// 从数据库获取订单详情
MchOrderInfoDTO orderDetail = shopOrderBaseMapper.getMchOrderDetail(orderId, expireSeconds);
// 如果查询结果为空直接返回
if (orderDetail == null) {
return null;
}
// 处理物流轨迹信息仅限已发货/已完成订单¬
Integer orderStateId = orderDetail.getOrder_state_id();
Integer deliveryTypeId = orderDetail.getDelivery_type_id();
// 确保订单状态和配送方式都不为空
if (orderStateId != null && deliveryTypeId != null) {
// 处理普通快递物流轨迹
if (CommonService.isNormalExpress(deliveryTypeId)
&& orderStateId >= StateCode.ORDER_STATE_SHIPPED
&& orderStateId <= StateCode.ORDER_STATE_FINISH) {
try {
orderDetail.setLogistics_traces(kdApiExpressSearchService.getLogisticsTraces(orderDetail.getOrder_id()));
} catch (Exception e) {
logger.error("获取普通快递物流轨迹失败订单ID{}", orderDetail.getOrder_id(), e);
}
}
// 处理顺丰同城物流轨迹
else if (CommonService.isSFExpress(deliveryTypeId)
&& orderStateId >= StateCode.ORDER_STATE_SHIPPED
&& orderStateId <= StateCode.ORDER_STATE_FINISH
&& orderDetail.getSf_order_info() != null
&& orderDetail.getSf_order_info().getFeed() == null) {
// 获取顺丰同城的物流轨迹
Map<String, Object> params = new HashMap<>();
params.put("order_id", orderDetail.getSf_order_info().getSf_order_id());
try {
ThirdApiRes feedRes = sfExpressApiService.listOrderFeed(params);
logger.info("获取顺丰同城配送员物流轨迹:{}", feedRes);
if (feedRes != null && feedRes.getError_code() != null && feedRes.getError_code().equals(0)) {
JSONObject result = JSONUtil.parseObj(feedRes.getResult());
if (result != null && result.get("feed") != null) {
orderDetail.getSf_order_info().setFeed(JSONUtil.toJsonStr(result.get("feed")));
}
}
} catch (Exception e) {
logger.error("获取顺丰同城物流轨迹失败订单ID{}", orderDetail.getOrder_id(), e);
}
}
}
// 格式化取货号
orderDetail.setOrder_pickup_num_str(fmtPickNum(orderDetail.getOrder_pickup_num()));
return orderDetail;
}

View File

@ -1779,6 +1779,7 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl<ShopOrderReturnM
item.put("return_reason_name", reason.getReturn_reason_name());
item.put("order_is_shipped", orderInfo.getOrder_is_shipped());
item.put("delivery_type_id", orderInfo.getDelivery_type_id());
String return_id = Convert.toStr(item.get("return_id"));
@ -1841,6 +1842,13 @@ public class ShopOrderReturnServiceImpl extends BaseServiceImpl<ShopOrderReturnM
String returnId = transferReturnsMap.get(return_id);
item.put("has_supplier_trans", returnId != null ? returnId : item.get("return_is_transfer"));
// 收货人信息 2025-08-04
ShopOrderDeliveryAddress deliveryAddress = orderDeliveryAddressService.get(order_id);
if (deliveryAddress != null) {
Map order_delivery = Convert.toMap(String.class, Object.class, deliveryAddress);
item.putAll(order_delivery);
}
}
data.put("items", accountService.fixUserAvatar(items, false));

View File

@ -13,17 +13,16 @@ import com.suisung.mall.shop.sfexpress.service.SFExpressApiService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.concurrent.CopyOnWriteArrayList;
@Api(tags = "顺丰同城Api端")
@RestController
@RequestMapping("/shop/sf-express")
public class SFExpressApiController {
private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();
//private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@Lazy
@Autowired
private SFExpressApiService sfExpressApiService;

View File

@ -16,6 +16,21 @@ import java.util.Map;
public interface SFExpressApiService {
/**
* 创建顺丰同店铺-连锁店铺
*
* @param storeId 商家门店ID
* @param shopName 店名
* @param cityName 城市
* @param shopAddress 店铺详细地址
* @param contactName 店铺联系人
* @param contactPhone 店铺电话
* @param longitude 经度
* @param latitude 纬度
* @return
*/
Pair<Boolean, String> createSfExpressShop(Integer storeId, String shopName, String cityName, String shopAddress, String contactName, String contactPhone, String longitude, String latitude);
/**
* 店铺创建顺丰同城订单
*

View File

@ -23,6 +23,7 @@ import com.suisung.mall.common.modules.store.ShopStoreSameCityTransportBase;
import com.suisung.mall.common.modules.store.ShopStoreSfOrder;
import com.suisung.mall.common.pojo.req.*;
import com.suisung.mall.common.pojo.res.ThirdApiRes;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.CommonUtil;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.JsonUtil;
@ -34,6 +35,7 @@ import com.suisung.mall.shop.sfexpress.service.SFExpressApiService;
import com.suisung.mall.shop.store.service.ShopStoreSameCityTransportBaseService;
import com.suisung.mall.shop.store.service.ShopStoreSfOrderService;
import com.suisung.mall.shop.wechat.service.WxOrderShippingService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -59,16 +61,22 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
private String appKey;
@Value("${sf-express.dev_id}")
private Integer devId;
@Value("${sf-express.supplier_id}")
private Integer supplierId;
@Lazy
@Autowired
private ShopStoreSfOrderService shopStoreSfOrderService;
@Lazy
@Autowired
private ShopOrderBaseService shopOrderBaseService;
@Lazy
@Autowired
private ShopOrderInfoService shopOrderInfoService;
@Lazy
@Autowired
private ShopOrderReturnService shopOrderReturnService;
@ -88,6 +96,100 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
// @Autowired
// private GeTuiPushService geTuiPushService;
/**
* 创建顺丰同店铺-连锁店铺
*
* @param storeId 商家门店ID
* @param shopName 店名
* @param cityName 城市
* @param shopAddress 店铺详细地址
* @param contactName 店铺联系人
* @param contactPhone 店铺电话
* @param longitude 经度
* @param latitude 纬度
* @return
*/
@Override
public Pair<Boolean, String> createSfExpressShop(Integer storeId, String shopName, String cityName, String shopAddress, String contactName, String contactPhone, String longitude, String latitude) {
logger.info("开始创建顺丰同城店铺");
if (CheckUtil.isEmpty(storeId) && StringUtils.isAnyBlank(shopName, shopAddress, contactName, contactPhone)) {
return Pair.of(false, "顺丰同城店铺,缺少必要参数!");
}
ShopStoreSameCityTransportBase shopStoreSameCityTransportBase = shopStoreSameCityTransportBaseService.getShopStoreSameCityTransportBaseById(Long.valueOf(storeId));
if (shopStoreSameCityTransportBase == null) {
Pair<Boolean, String> result = shopStoreSameCityTransportBaseService.initDefaultSameCityTransport(storeId);
if (!result.getFirst()) {
return result;
}
shopStoreSameCityTransportBase = shopStoreSameCityTransportBaseService.getShopStoreSameCityTransportBaseById(Long.valueOf(storeId));
}
if (CheckUtil.isNotEmpty(shopStoreSameCityTransportBase.getShop_id())) {
return Pair.of(true, "顺丰同城已创建过该店铺!");
}
Map<String, Object> params = buildCommonParams();
params.put("supplier_id", supplierId);
params.put("out_shop_id", storeId);
params.put("shop_name", shopName); //店铺名称不可重复
params.put("city_name", cityName);
//1:快餐 2:药品 3:百货 4:脏衣服收 5:干净衣服派 6:生鲜 8:高端饮品 9:现场勘验 10:快递 12:文件 13:蛋糕 14:鲜花 15:数码 16:服装 17:
//汽配 18:珠宝 20:披萨 21:中餐 22:水产 27:专人直送 32:中端饮品 33:便利店 34:面包糕点 35:火锅 36:证照 40:烧烤小龙虾 41:外部落地配 47:烟酒
// 48:成人用品 99:其他
params.put("shop_product_types", "1,3"); // 店铺经营类型支持多品类枚举值见下方
params.put("shop_type", 1);// 店铺类型:1-普通型 2-平台型
params.put("shop_address", shopAddress);
params.put("longitude", longitude);
params.put("latitude", latitude);
params.put("shop_contact_name", contactName);
params.put("shop_contact_phone", contactPhone);
// 请求参数转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("createShop", paramJSON);
// 向顺丰同城 创建一个顺丰同城配送订单
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("创建顺丰同城店铺异常,无返回值!");
return Pair.of(false, "创建顺丰同城店铺异常,无返回值!");
}
JSONObject sfExpressApiRes = JSONUtil.parseObj(retRespStr);
if (sfExpressApiRes == null) {
logger.error("创建顺丰同城店铺异常,返回值有误!!");
return Pair.of(false, "创建顺丰同城店铺异常,返回值有误!");
}
if (!sfExpressApiRes.get("error_code").equals(0) || sfExpressApiRes.get("result") == null) {
logger.error("创建顺丰同城店铺失败: {}", sfExpressApiRes.get("error_msg"));
return Pair.of(false, Convert.toStr(sfExpressApiRes.get("error_msg")));
}
// TODO 判断状态
String sfShopId = sfExpressApiRes.getByPath("result.shop_id").toString();
shopStoreSameCityTransportBase.setShop_id(sfShopId);
shopStoreSameCityTransportBaseService.saveOrUpdateShopStoreSameCityTransportBase(shopStoreSameCityTransportBase);
// 个推推送消息
// JSONObject payload = new JSONObject();
// payload.put("category", CommonConstant.PUSH_MSG_CATE_CREATE_SF_SHOP);
// payload.put("sfShopId", sfShopId);
// pushMessageService.sendMessage(null, sfShopId, "您有一笔新的订单", "您有一笔同城订单[" + shopOrderId + "],请及时处理。", payload);
//
return Pair.of(true, "创建顺丰同城店铺成功!");
}
@Override
public ThirdApiRes createOrder(String shopOrderId) {
// 组织请求参数
@ -583,6 +685,17 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
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")));
}
}
// 个推推送消息
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);
@ -616,11 +729,12 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
// 更改顺丰同城订单状态
ShopStoreSfOrder shopStoreSfOrder = toShopStoreSfOrder(jsonData);
String shopOrderId = shopStoreSfOrder.getShop_order_id();
// 获取顺丰同城的物流轨迹
Map<String, Object> params = new HashMap<>();
String orderId = shopStoreSfOrder.getShop_order_id();
params.put("order_id", orderId);
String sfOrderId = shopStoreSfOrder.getSf_order_id(); // 顺丰订单Id
params.put("order_id", sfOrderId);
ThirdApiRes feedRes = listOrderFeed(params);
// logger.debug("获取配送员物流轨迹:{}", feedRes);
if (feedRes != null && feedRes.getError_code().equals(0)) {
@ -689,8 +803,8 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
// 消息推送
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);
payload.put("orderId", orderId);
pushMessageService.noticeMerchantEmployeeOrderAction(null, orderId, "", "顺丰同城订单[" + orderId + "]" + pushRemark, payload);
payload.put("orderId", shopOrderId);
pushMessageService.noticeMerchantEmployeeOrderAction(null, shopOrderId, "", "顺丰同城订单[" + shopOrderId + "]" + pushRemark, payload);
return new ThirdApiRes().success("success");
}

View File

@ -73,13 +73,15 @@ public interface ShopMchEntryService {
/**
* 检查手机和营业执照是否已经申请过
* 检查商家是否申请过入驻企业或个人的判断条件不一样的企业判断营业执照号+手机号个人判断个人身份证号+手机号
*
* @param mobile
* @param bizLicenseNumber
* @param entityType 入驻主体类型企业或个人1-企业2-个人
* @param contactMobile 联系人手机号
* @param bizLicenseNumber 企业营业执照企业必填
* @param individualIdNumber 个人身份证号码个人必填
* @return
*/
Pair<Boolean, String> isApplied(String mobile, String bizLicenseNumber);
Pair<Boolean, String> isApplied(Integer entityType, String contactMobile, String bizLicenseNumber, String individualIdNumber);
/**
* 通过手机号获取商家入驻审核状态

View File

@ -173,8 +173,22 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl<ShopMchEntryMapper,
return CommonResult.failed("申请人手机号码有误!");
}
// 检查店铺是否已经申请过入驻
Pair<Boolean, String> isApplied = isApplied(loginMobile, record.getBiz_license_number());
if (CheckUtil.isEmpty(record.getEntity_type())) {
return CommonResult.failed("请选择是企业还是个人");
}
// 不管是企业或个人只要没有填写联系人就直接把商家手机号码作为联系人手机号码
if (StrUtil.isBlank(record.getLegal_person_mobile())) {
record.setLegal_person_mobile(loginMobile);
}
// 检查店铺是否已经申请过入驻企业和个人判断条件是不一样的
// * 检查商家是否已经申请过入驻
// * 根据入驻主体类型企业或个人使用不同的判断条件
// * - 企业通过营业执照号+联系人手机号判断
// * - 个人通过个人身份证号+联系人手机号判断
Pair<Boolean, String> isApplied = isApplied(record.getEntity_type(), record.getLegal_person_mobile(), record.getBiz_license_number(), record.getIndividual_id_number());
if (isApplied.getFirst()) {
return CommonResult.failed(isApplied.getSecond());
}
@ -185,7 +199,6 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl<ShopMchEntryMapper,
}
Boolean isQy = true;
// 检查企业法人或个人的营业执照或身份证
if (ObjectUtil.isNotEmpty(record.getEntity_type()) && record.getEntity_type().equals(CommonConstant.MCH_ENTITY_TYPE_GR)) {
isQy = false; // 个人入驻
@ -758,41 +771,86 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl<ShopMchEntryMapper,
}
/**
* 检查手机和营业执照是否已经申请过
* 检查商家是否已经申请过入驻
* 根据入驻主体类型企业或个人使用不同的判断条件
* - 企业通过营业执照号+联系人手机号判断
* - 个人通过个人身份证号+联系人手机号判断
*
* @param mobile
* @param bizLicenseNumber
* @return
* @param entityType 入驻主体类型企业或个人1-企业2-个人
* @param contactMobile 联系人手机号
* @param bizLicenseNumber 企业营业执照号企业必填
* @param individualIdNumber 个人身份证号码个人必填
* @return Pair<Boolean, String> 第一个值表示是否已申请true表示已申请第二个值为提示信息
*/
@Override
public Pair<Boolean, String> isApplied(String mobile, String bizLicenseNumber) {
if (StrUtil.isBlank(mobile) && StrUtil.isBlank(bizLicenseNumber)) {
return Pair.of(false, "缺少必要参数!");
public Pair<Boolean, String> isApplied(Integer entityType, String contactMobile, String bizLicenseNumber, String individualIdNumber) {
// 1. 参数校验入驻主体类型不能为空
if (CheckUtil.isEmpty(entityType)) {
return Pair.of(Boolean.FALSE, "请选择是企业还是个人");
}
QueryWrapper<ShopMchEntry> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("status", CommonConstant.Enable);
boolean isApplied = false;
String msg = "";
if (StrUtil.isNotBlank(mobile)) {
queryWrapper.eq("login_mobile", mobile);
isApplied = count(queryWrapper) > 0;
if (isApplied) msg = "手机已申请过入驻!";
queryWrapper.clear();
}
if (StrUtil.isNotBlank(bizLicenseNumber)) {
queryWrapper.eq("biz_license_number", bizLicenseNumber);
boolean licenseApplied = count(queryWrapper) > 0;
if (licenseApplied) {
isApplied = true;
msg = StrUtil.isNotBlank(msg) ? "手机和营业执照已申请过入驻!" : "营业执照已申请过入驻!";
// 2. 根据不同主体类型校验必要参数
if (entityType.equals(CommonConstant.MCH_ENTITY_TYPE_QY)) {
// 企业类型营业执照号和联系人手机号至少提供一个
if (StrUtil.isBlank(contactMobile) && StrUtil.isBlank(bizLicenseNumber)) {
return Pair.of(Boolean.FALSE, "缺少企业营业执照或联系人手机号!");
}
} else if (entityType.equals(CommonConstant.MCH_ENTITY_TYPE_GR)) {
// 个人类型身份证号和联系人手机号至少提供一个
if (StrUtil.isBlank(contactMobile) && StrUtil.isBlank(individualIdNumber)) {
return Pair.of(Boolean.FALSE, "缺少个人身份证号或联系人手机号!");
}
} else {
// 未知的主体类型
return Pair.of(Boolean.FALSE, "不支持的入驻主体类型");
}
return Pair.of(isApplied, msg);
try {
// 3. 构建查询条件
QueryWrapper<ShopMchEntry> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("entity_type", entityType)
.eq("status", CommonConstant.Enable);
// 必须包含联系人手机号
if (StrUtil.isBlank(contactMobile)) {
return Pair.of(Boolean.FALSE, "联系人手机号不能为空");
}
queryWrapper.eq("legal_person_mobile", contactMobile);
String msg = "";
// 4. 根据主体类型设置额外查询条件和提示信息
if (entityType.equals(CommonConstant.MCH_ENTITY_TYPE_QY)) {
// 企业类型增加营业执照号查询条件
if (StrUtil.isBlank(bizLicenseNumber)) {
return Pair.of(Boolean.FALSE, "企业营业执照号不能为空");
}
queryWrapper.eq("biz_license_number", bizLicenseNumber);
msg = "营业执照号已提交过入驻";
} else {
// 个人类型增加身份证号查询条件
if (StrUtil.isBlank(individualIdNumber)) {
return Pair.of(Boolean.FALSE, "个人身份证号不能为空");
}
queryWrapper.eq("individual_id_number", individualIdNumber);
msg = "个人身份证已提交过入驻";
}
// 5. 执行查询并返回结果
long count = count(queryWrapper);
boolean isApplied = count > 0;
if (isApplied) {
return Pair.of(Boolean.FALSE, msg);
}
return Pair.of(Boolean.TRUE, "");
} catch (Exception e) {
// 6. 异常处理记录日志并返回错误信息
log.error("检查商家入驻申请状态时发生异常: entityType={}, contactMobile={}, bizLicenseNumber={}, individualIdNumber={}",
entityType, contactMobile, bizLicenseNumber, individualIdNumber, e);
return Pair.of(Boolean.FALSE, "系统异常,请稍后重试");
}
}
/**

View File

@ -1611,6 +1611,14 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
return CommonResult.success(data, I18nUtil._("修改店铺状态成功!"));
}
/**
* 保存店铺基础信息, 包括新增管理员和密码
*
* @param shopStoreBase
* @param user_account
* @param user_password
* @return
*/
@GlobalTransactional
@Override
public CommonResult saveOrUpdateBase(ShopStoreBase shopStoreBase, String user_account, String user_password) {

View File

@ -139,6 +139,7 @@ feieyun:
sf-express:
# 顺丰同城 api 接口配置
appid: 1711573316
supplier_id: 0
appkey: cd57608baa9c00fe1cda5f652b14240d
dev_id: 1711573316
enable: 2

View File

@ -139,6 +139,7 @@ feieyun:
sf-express:
# 顺丰同城 api 接口配置
appid: 1711573316
supplier_id: 0
appkey: cd57608baa9c00fe1cda5f652b14240d
dev_id: 1711573316
enable: 2

View File

@ -145,6 +145,7 @@ feieyun:
sf-express:
# 顺丰同城 api 接口配置
dev_id: 1711573316
supplier_id: 0
appid: 1711573316
appkey: cd57608baa9c00fe1cda5f652b14240d
# dev_id: 1715091463
@ -176,7 +177,7 @@ lakala:
user_no: 22874827
split_lowest_ratio: 70.00
activity_id: 208
wx_fee: 0.25 # 微信手续费 6/1000
wx_fee: 0.25 # 微信手续费 2.5/1000
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

View File

@ -143,6 +143,7 @@ feieyun:
sf-express:
# 顺丰同城 api 接口配置
appid: 1711573316
supplier_id: 0
appkey: cd57608baa9c00fe1cda5f652b14240d
dev_id: 1711573316
enable: 2

View File

@ -143,6 +143,7 @@ feieyun:
sf-express:
# 顺丰同城 api 接口配置
appid: 1711573316
supplier_id: 0
appkey: cd57608baa9c00fe1cda5f652b14240d
dev_id: 1711573316
enable: 2

View File

@ -625,6 +625,7 @@
<result property="h5_url" column="h5_url"/>
<result property="feed" column="feed"/>
</association>
<!-- 订单商品集合映射-->
<collection property="order_items" ofType="com.suisung.mall.common.modules.order.dto.MchOrderItemDTO">
<result property="product_id" column="product_id"/>