|
|
@@ -8,10 +8,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
|
|
|
import com.ylx.address.domain.TAddress;
|
|
|
import com.ylx.address.service.TAddressService;
|
|
|
+import com.ylx.base.BaseServiceImpl;
|
|
|
import com.ylx.common.core.domain.entity.SysDictData;
|
|
|
import com.ylx.common.core.domain.model.WxLoginUser;
|
|
|
import com.ylx.common.exception.ServiceException;
|
|
|
@@ -50,8 +50,6 @@ import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
|
|
|
import com.ylx.shopingfundsdetail.enums.ShoppingFundsExpenseTypeEnum;
|
|
|
import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.beans.BeanUtils;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -76,7 +74,7 @@ import static com.ylx.order.constant.OrderConstant.*;
|
|
|
*/
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
-public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
|
|
|
+public class TOrderServiceImpl extends BaseServiceImpl<TOrderMapper, TOrder> implements TOrderService {
|
|
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
@@ -99,35 +97,41 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
private CouponService couponService;
|
|
|
@Resource
|
|
|
private WxPayV3Service wxPayV3Service;
|
|
|
-
|
|
|
@Resource
|
|
|
private OrderStatusFlowService orderStatusFlowService;
|
|
|
-
|
|
|
@Resource
|
|
|
private MaTechnicianMapper maTechnicianMapper;
|
|
|
-
|
|
|
@Resource
|
|
|
private MaProjectMapper maProjectMapper;
|
|
|
@Resource
|
|
|
private IAfterSalesServiceService afterSalesServiceService;
|
|
|
@Resource
|
|
|
private IMessageService messageService;
|
|
|
-
|
|
|
@Resource
|
|
|
private TGeoFenceService geoFenceService;
|
|
|
-
|
|
|
- @Value("${ylx.sendSmsEnabled:false}")
|
|
|
- private boolean sendSmsEnabled;
|
|
|
-
|
|
|
- @Value("${ylx.fixedVerifyCode:123456}")
|
|
|
- private String fixedVerifyCode;
|
|
|
-
|
|
|
@Resource
|
|
|
private IMaProjectFareSettingService maProjectFareSettingService;
|
|
|
-
|
|
|
@Resource
|
|
|
private RedisTemplate<String, String> redisTemplate;
|
|
|
|
|
|
+ private static final Map<Integer, String> unitDictCache = new HashMap<>();
|
|
|
+
|
|
|
+ static {
|
|
|
+ List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
+ if (CollUtil.isNotEmpty(dictList)) {
|
|
|
+ dictList.forEach(dict -> {
|
|
|
+ try {
|
|
|
+ Integer key = Integer.parseInt(dict.getDictValue());
|
|
|
+ unitDictCache.put(key, dict.getDictLabel());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("单位字典值非数字,跳过该条记录:{}", dict);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ log.warn("项目计量单位字典未加载,请检查配置:{}", DICT_UNIT_TYPE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Map<String, Object> submitOrder(OrderSubmitDTO dto) {
|
|
|
@@ -170,8 +174,6 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
if (ObjectUtil.isNotNull(dto.getCouponId())) {
|
|
|
// 计算抵扣金额
|
|
|
couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), openId, basePrice);
|
|
|
- // 立即核销优惠券,保证与订单在同一事务中
|
|
|
- this.couponService.useCoupon(dto.getCouponId(), openId, null, 1);
|
|
|
}
|
|
|
|
|
|
// 实付金额 = 商品原价 - 优惠券优惠 + 车费
|
|
|
@@ -249,7 +251,6 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
|
|
|
// 5. 返回分页结果
|
|
|
-
|
|
|
Page<OrderDateQueryVo> voPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
|
|
|
voPage.setRecords(voList);
|
|
|
|
|
|
@@ -347,7 +348,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
private OrderDateQueryVo convertToVo(TOrder order) {
|
|
|
OrderDateQueryVo vo = new OrderDateQueryVo();
|
|
|
// 拷贝相同字段
|
|
|
- BeanUtils.copyProperties(order, vo);
|
|
|
+ BeanUtil.copyProperties(order, vo);
|
|
|
// 手动设置字段名不一致或需要特殊处理的
|
|
|
vo.setOrderStatus(order.getStatus());
|
|
|
vo.setOrderStatusName(getOrderStatusName(order.getStatus()));
|
|
|
@@ -422,11 +423,11 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
order.setUserLatitude(address.getLatitude());
|
|
|
order.setUserLongitude(address.getLongitude());
|
|
|
|
|
|
- merchantAddressList.stream().filter(a -> a.getType() == 1).findFirst().ifPresent(addr -> {
|
|
|
+ merchantAddressList.stream().filter(a -> a.getType().equals(ADDR_TYPE_REAL)).findFirst().ifPresent(addr -> {
|
|
|
order.setMerchantLongitude(addr.getLongitude());
|
|
|
order.setMerchantLatitude(addr.getLatitude());
|
|
|
});
|
|
|
- merchantAddressList.stream().filter(a -> a.getType() == 2).findFirst().ifPresent(addr -> {
|
|
|
+ merchantAddressList.stream().filter(a -> a.getType().equals(ADDR_TYPE_VIRTUAL)).findFirst().ifPresent(addr -> {
|
|
|
order.setVirtualLongitude(addr.getLongitude());
|
|
|
order.setVirtualLatitude(addr.getLatitude());
|
|
|
});
|
|
|
@@ -446,9 +447,12 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
*/
|
|
|
private void handleBalancePayment(Long userId, BigDecimal finalAmount, TOrder order) {
|
|
|
// 1. 数据库层面原子扣减余额,并校验余额是否充足
|
|
|
- boolean deductSuccess = this.wxUserService.lambdaUpdate().setSql("d_balance = d_balance - " + finalAmount).eq(TWxUser::getId, userId)
|
|
|
- // 防止余额超扣的核心:当前余额必须大于等于扣减金额
|
|
|
- .apply("d_balance >= {0}", finalAmount).update();
|
|
|
+ boolean deductSuccess = wxUserService.lambdaUpdate()
|
|
|
+ .setSql("d_balance = d_balance - {0}")
|
|
|
+ .eq(TWxUser::getId, userId)
|
|
|
+ // 两个位置共用同一个参数,逗号传参
|
|
|
+ .apply("d_balance >= {0}", finalAmount)
|
|
|
+ .update();
|
|
|
|
|
|
if (!deductSuccess) {
|
|
|
throw new ServiceException("余额不足,扣款失败");
|
|
|
@@ -530,8 +534,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
// 查询技师表
|
|
|
if (order.getMerchantId() != null) {
|
|
|
- // merchantId 为 Long,技师表 id 为 Integer,需转换
|
|
|
- MaTechnician technician = maTechnicianMapper.selectById(order.getMerchantId().intValue());
|
|
|
+ MaTechnician technician = maTechnicianMapper.selectById(order.getMerchantId());
|
|
|
if (technician != null) {
|
|
|
//评分
|
|
|
vo.setStar(technician.getNStar());
|
|
|
@@ -825,8 +828,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
@Override
|
|
|
public OrderCustomerPhoneVO getCustomerPhoneByOrderId(OrderPhoneQueryDTO dto) {
|
|
|
// 1. 获取当前登录商户
|
|
|
- WxLoginUser wxLoginUser = getCurrentWxLoginUser();
|
|
|
- Long merchantId = Long.parseLong(wxLoginUser.getId());
|
|
|
+ Long merchantId = this.getCurrentUserId();
|
|
|
Long orderId = dto.getOrderId();
|
|
|
|
|
|
// 2. 查询订单:只查归属商户、未删除、手机号字段
|
|
|
@@ -1071,7 +1073,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
throw new ServiceException("仅【待出发】订单可签到,当前状态:" + ExecStatusEnum.getInfoByCode(execStatus));
|
|
|
}
|
|
|
// 上门签到:执行全部手机号校验、状态强限制、写入signTime
|
|
|
- if (phoneTail.length() != 4 || !StrUtil.isNumeric(phoneTail)) {
|
|
|
+ if (phoneTail.length() != PHONE_TAIL_LENGTH || !StrUtil.isNumeric(phoneTail)) {
|
|
|
throw new ServiceException("手机号后4位必须为4位数字");
|
|
|
}
|
|
|
if (!PhoneUtils.matchPhoneTail4(order.getContactPhoneNumber(), phoneTail)) {
|
|
|
@@ -1097,8 +1099,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
public Page<RecentMerchantVO> getRecentMerchants(RecentOrderQueryDTO dto) {
|
|
|
|
|
|
// 1. 获取当前登录用户
|
|
|
- WxLoginUser wxLoginUser = this.getCurrentWxLoginUser();
|
|
|
- Long userId = Long.parseLong(wxLoginUser.getId());
|
|
|
+ Long userId = this.getCurrentUserId();
|
|
|
dto.setUserId(userId);
|
|
|
|
|
|
// 2. 构建分页对象
|
|
|
@@ -1161,15 +1162,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
|
|
|
Integer standardDuration = project.getStandardDuration();
|
|
|
Integer unitType = project.getUnitType();
|
|
|
- String unitTypeName = "未知单位";
|
|
|
- List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
- if (CollUtil.isNotEmpty(dictList) && ObjectUtil.isNotNull(unitType)) {
|
|
|
- unitTypeName = dictList.stream()
|
|
|
- .filter(dict -> unitType.toString().equals(dict.getDictValue()))
|
|
|
- .map(SysDictData::getDictLabel)
|
|
|
- .findFirst()
|
|
|
- .orElse("未知单位");
|
|
|
- }
|
|
|
+ String unitTypeName = unitDictCache.getOrDefault(unitType, "未知单位");
|
|
|
|
|
|
Integer durationMinutes = convertToMinutes(standardDuration, unitType);
|
|
|
if (ObjectUtil.isNull(durationMinutes) || durationMinutes <= 0) {
|
|
|
@@ -1177,7 +1170,7 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
}
|
|
|
|
|
|
// 2. 生成所有候选开始时间(半点粒度,步进30分钟)
|
|
|
- List<LocalTime> candidateStarts = TimeSlotUtil.getAvailableTimeSlots(dto.getAppointmentDate(), 30);
|
|
|
+ List<LocalTime> candidateStarts = TimeSlotUtil.getAvailableTimeSlots(dto.getAppointmentDate(), APPOINT_BUFFER_MINUTES);
|
|
|
|
|
|
// 3. 查询该商户当天的【有效占用】订单
|
|
|
LocalDateTime dayStart = dto.getAppointmentDate().atStartOfDay();
|
|
|
@@ -1296,27 +1289,8 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
return duration;
|
|
|
}
|
|
|
|
|
|
- // 2. 从字典缓存中获取数据(若依的 DictUtils 读取成本极低,无需自己做 DCL 锁)
|
|
|
- List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
- if (CollUtil.isEmpty(dictList)) {
|
|
|
- return duration;
|
|
|
- }
|
|
|
-
|
|
|
- // 3. 使用局部变量构建映射,彻底杜绝多线程并发修改 HashMap 的风险
|
|
|
- Map<Integer, Integer> localCoeffMap = new HashMap<>(dictList.size());
|
|
|
- for (SysDictData data : dictList) {
|
|
|
- try {
|
|
|
- int type = Integer.parseInt(data.getDictValue());
|
|
|
- // 根据业务规则设置倍率
|
|
|
- int ratio = (type == 1) ? 1 : 60;
|
|
|
- localCoeffMap.put(type, ratio);
|
|
|
- } catch (NumberFormatException e) {
|
|
|
- log.warn("字典数据格式错误,跳过该条记录: {}", data);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 获取倍率并计算,默认倍率为 1(即不转换)
|
|
|
- int factor = localCoeffMap.getOrDefault(unitType, 1);
|
|
|
+ // 2. 静态缓存已全局加载完成,直接判断
|
|
|
+ int factor = unitDictCache.containsKey(unitType) && unitType == 1 ? 1 : 60;
|
|
|
return duration * factor;
|
|
|
}
|
|
|
|
|
|
@@ -1350,15 +1324,6 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
order.setPlatformIncome(platformIncome);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- private WxLoginUser getCurrentWxLoginUser() {
|
|
|
- WxLoginUser loginUser = SecurityUtils.getWxLoginUser();
|
|
|
- if (ObjectUtil.isNull(loginUser)) {
|
|
|
- throw new ServiceException("用户未登录或登录已过期");
|
|
|
- }
|
|
|
- return loginUser;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 抽取公共校验逻辑:校验订单是否存在、是否被删除、是否属于当前商户
|
|
|
*/
|
|
|
@@ -1376,32 +1341,13 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
throw new ServiceException("订单已被删除");
|
|
|
}
|
|
|
|
|
|
- if (ObjectUtil.notEqual(order.getMerchantId(), merchantId)) {
|
|
|
+ if (ObjectUtil.isNotNull(merchantId) && ObjectUtil.notEqual(order.getMerchantId(), merchantId)) {
|
|
|
throw new ServiceException("您无权操作此订单");
|
|
|
}
|
|
|
|
|
|
return order;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 校验验证码
|
|
|
- *
|
|
|
- * @param phone 手机号
|
|
|
- * @param code 用户输入的验证码
|
|
|
- * @return boolean 校验是否通过
|
|
|
- */
|
|
|
- private boolean validateVerifyCode(String phone, String code) {
|
|
|
- if (sendSmsEnabled) {
|
|
|
- // TODO: 从Redis或其他存储中获取真实发送的验证码进行校验
|
|
|
- // String realCode = redisCache.getCacheObject("sms:bindPhone:" + phone);
|
|
|
- // return code.equals(realCode);
|
|
|
- return true;
|
|
|
- } else {
|
|
|
- // 使用写死的验证码进行校验
|
|
|
- return fixedVerifyCode.equals(code);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 计算用户客户标签编码
|
|
|
*
|