重构订单创建逻辑:采用Builder模式替代20+参数方法

主要改进:
- 新增OrderCreationRequest及相关DTO类,使用Builder模式提升代码可读性
- 引入类型安全的枚举类OrderConstant,替代魔法字符串常量
- 重构PlayOrderInfoServiceImpl,新增基于Builder模式的createOrderInfo方法
- 保留原有方法并标记为@Deprecated,确保向后兼容性
- 完善单元测试覆盖,包含Mockito模拟和边界条件测试
- 优化包结构,将DTO类从vo包迁移到dto包
- 添加JUnit 5和Mockito测试依赖
- 移除实体类过度使用的Lombok注解,改用精简的自定义构造器
- 新增数据库开发工作流程文档

技术栈:
- Spring Boot 2.7.9
- MyBatis-Plus 3.5.3.2
- JUnit 5 + Mockito
- Lombok Builder模式
- 类型安全枚举设计
This commit is contained in:
irving
2025-09-06 22:58:14 -04:00
parent 6194c64b4f
commit 295400b83e
11 changed files with 1391 additions and 31 deletions

View File

@@ -1,44 +1,136 @@
package com.starry.admin.modules.order.module.constant;
import lombok.Getter;
/**
* 订单相关枚举和常量
*
* @author admin
* @since 2024/5/8 15:41
**/
*/
public class OrderConstant {
/**
* 订单状态-待接单
*
* @since 2024/5/8 15:42
**/
* 订单状态枚举
*/
@Getter
public enum OrderStatus {
PENDING("0", "已下单(待接单)"),
ACCEPTED("1", "已接单(待开始)"),
IN_PROGRESS("2", "已开始(服务中)"),
COMPLETED("3", "已完成"),
CANCELLED("4", "已取消");
private final String code;
private final String description;
OrderStatus(String code, String description) {
this.code = code;
this.description = description;
}
public static OrderStatus fromCode(String code) {
for (OrderStatus status : values()) {
if (status.code.equals(code)) {
return status;
}
}
throw new IllegalArgumentException("Unknown order status code: " + code);
}
}
/**
* 订单类型枚举
*/
@Getter
public enum OrderType {
REFUND("-1", "退款订单"),
RECHARGE("0", "充值订单"),
WITHDRAWAL("1", "提现订单"),
NORMAL("2", "普通订单");
private final String code;
private final String description;
OrderType(String code, String description) {
this.code = code;
this.description = description;
}
public static OrderType fromCode(String code) {
for (OrderType type : values()) {
if (type.code.equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Unknown order type code: " + code);
}
}
/**
* 下单类型枚举
*/
@Getter
public enum PlaceType {
OTHER("-1", "其他类型"),
SPECIFIED("0", "指定单"),
RANDOM("1", "随机单"),
REWARD("2", "打赏单");
private final String code;
private final String description;
PlaceType(String code, String description) {
this.code = code;
this.description = description;
}
public static PlaceType fromCode(String code) {
for (PlaceType type : values()) {
if (type.code.equals(code)) {
return type;
}
}
throw new IllegalArgumentException("Unknown place type code: " + code);
}
}
/**
* 性别枚举
*/
@Getter
public enum Gender {
UNKNOWN("0", "未知"),
MALE("1", ""),
FEMALE("2", "");
private final String code;
private final String description;
Gender(String code, String description) {
this.code = code;
this.description = description;
}
public static Gender fromCode(String code) {
for (Gender gender : values()) {
if (gender.code.equals(code)) {
return gender;
}
}
throw new IllegalArgumentException("Unknown gender code: " + code);
}
}
// Legacy constants for backward compatibility - consider deprecating
@Deprecated
public final static String ORDER_STATUS_0 = "0";
/**
* 订单状态-待开始
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_1 = "1";
/**
* 订单状态-服务中
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_2 = "2";
/**
* 订单状态-已完成
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_3 = "3";
/**
* 订单状态-已取消
*
* @since 2024/5/8 15:42
**/
@Deprecated
public final static String ORDER_STATUS_4 = "4";
}

View File

@@ -0,0 +1,44 @@
package com.starry.admin.modules.order.module.dto;
import java.math.BigDecimal;
import lombok.Builder;
import lombok.Data;
/**
* 商品信息值对象
*
* @author admin
*/
@Data
@Builder
public class CommodityInfo {
/**
* 商品ID
*/
private String commodityId;
/**
* 商品类型[0:礼物1服务]
*/
private String commodityType;
/**
* 商品单价
*/
private BigDecimal commodityPrice;
/**
* 商品属性-服务时长
*/
private String serviceDuration;
/**
* 商品名称
*/
private String commodityName;
/**
* 商品数量
*/
private String commodityNumber;
}

View File

@@ -0,0 +1,127 @@
package com.starry.admin.modules.order.module.dto;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
/**
* 订单创建请求对象 - 使用Builder模式替换20+参数的方法
*
* @author admin
*/
@Data
@Builder
public class OrderCreationRequest {
/**
* 订单ID
*/
@NotBlank(message = "订单ID不能为空")
private String orderId;
/**
* 订单编号
*/
@NotBlank(message = "订单编号不能为空")
private String orderNo;
/**
* 订单状态
*/
@NotNull(message = "订单状态不能为空")
private OrderConstant.OrderStatus orderStatus;
/**
* 订单类型
*/
@NotNull(message = "订单类型不能为空")
private OrderConstant.OrderType orderType;
/**
* 下单类型
*/
@NotNull(message = "下单类型不能为空")
private OrderConstant.PlaceType placeType;
/**
* 打赏类型0:余额;1:礼物)
*/
private String rewardType;
/**
* 是否是首单
*/
private boolean isFirstOrder;
/**
* 商品信息
*/
@Valid
@NotNull(message = "商品信息不能为空")
private CommodityInfo commodityInfo;
/**
* 支付信息
*/
@Valid
@NotNull(message = "支付信息不能为空")
private PaymentInfo paymentInfo;
/**
* 下单人
*/
@NotBlank(message = "下单人不能为空")
private String purchaserBy;
/**
* 接单人(可选)
*/
private String acceptBy;
/**
* 微信号码
*/
private String weiChatCode;
/**
* 订单备注
*/
private String remark;
/**
* 随机单要求(仅随机单时需要)
*/
private RandomOrderRequirements randomOrderRequirements;
/**
* 获取首单标识字符串(兼容现有系统)
*/
public String getFirstOrderString() {
return isFirstOrder ? "1" : "0";
}
/**
* 验证随机单要求
*/
public boolean isValidForRandomOrder() {
return placeType == OrderConstant.PlaceType.RANDOM
&& randomOrderRequirements != null;
}
/**
* 是否为打赏单
*/
public boolean isRewardOrder() {
return placeType == OrderConstant.PlaceType.REWARD;
}
/**
* 是否为指定单
*/
public boolean isSpecifiedOrder() {
return placeType == OrderConstant.PlaceType.SPECIFIED;
}
}

View File

@@ -0,0 +1,40 @@
package com.starry.admin.modules.order.module.dto;
import java.math.BigDecimal;
import java.util.List;
import lombok.Builder;
import lombok.Data;
/**
* 支付信息值对象
*
* @author admin
*/
@Data
@Builder
public class PaymentInfo {
/**
* 订单金额
*/
private BigDecimal orderMoney;
/**
* 订单最终金额(支付金额)
*/
private BigDecimal finalAmount;
/**
* 优惠金额
*/
private BigDecimal discountAmount;
/**
* 优惠券ID列表
*/
private List<String> couponIds;
/**
* 支付方式0余额支付,1:微信支付,2:支付宝支付
*/
private String payMethod;
}

View File

@@ -0,0 +1,36 @@
package com.starry.admin.modules.order.module.dto;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import lombok.Builder;
import lombok.Data;
/**
* 随机单要求信息值对象
*
* @author admin
*/
@Data
@Builder
public class RandomOrderRequirements {
/**
* 随机单要求-店员性别
*/
private OrderConstant.Gender clerkGender;
/**
* 随机单要求-店员等级ID
*/
private String clerkLevelId;
/**
* 随机单要求-是否排除下单过的成员0:不排除;1:排除)
*/
private String excludeHistory;
/**
* 是否排除历史订单
*/
public boolean shouldExcludeHistory() {
return "1".equals(excludeHistory);
}
}

View File

@@ -2,6 +2,7 @@ package com.starry.admin.modules.order.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.order.module.dto.*;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.module.vo.*;
import com.starry.admin.modules.weichat.entity.order.*;
@@ -43,8 +44,18 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
void createRechargeOrder(String orderNo, BigDecimal orderMoney, BigDecimal finalAmount, String purchaserBy);
/**
* 新增订单信息
* 新增订单信息 - 重构版本使用Builder模式
*
* @param request 订单创建请求对象
* @author admin
* @since 2024/6/3 10:53
**/
void createOrderInfo(OrderCreationRequest request);
/**
* 新增订单信息 - 旧版本方法已废弃建议使用OrderCreationRequest
*
* @deprecated 请使用 {@link #createOrderInfo(OrderCreationRequest)} 替代
* @param orderId
* 订单ID
* @param orderNo
@@ -96,6 +107,7 @@ public interface IPlayOrderInfoService extends IService<PlayOrderInfoEntity> {
* @author admin
* @since 2024/6/3 10:53
**/
@Deprecated
void createOrderInfo(String orderId, String orderNo, String orderState, String orderType, String placeType,
String rewardType, String firstOrder, String commodityId, String commodityType, BigDecimal commodityPrice,
String serviceDuration, String commodityName, String commodityNumber, BigDecimal orderMoney,

View File

@@ -19,6 +19,7 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.dto.*;
import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderEvaluateInfoEntity;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
@@ -158,6 +159,132 @@ public class PlayOrderInfoServiceImpl extends ServiceImpl<PlayOrderInfoMapper, P
playCouponDetailsService.updateCouponUseStateByIds(couponIds, "2");
}
@Override
public void createOrderInfo(OrderCreationRequest request) {
// 验证请求
validateOrderCreationRequest(request);
PlayOrderInfoEntity entity = buildOrderEntity(request);
// 处理随机单要求
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM) {
setRandomOrderRequirements(entity, request.getRandomOrderRequirements());
}
// 处理接单人信息
if (StrUtil.isNotBlank(request.getAcceptBy())) {
setAcceptByInfo(entity, request);
}
// 处理打赏单自动完成逻辑
if (request.isRewardOrder()) {
setRewardOrderCompleted(entity);
}
// 保存订单
userInfoService.saveOrderInfo(entity);
this.baseMapper.insert(entity);
// 修改优惠券状态
playCouponDetailsService.updateCouponUseStateByIds(
request.getPaymentInfo().getCouponIds(), "2");
}
/**
* 验证订单创建请求
*/
private void validateOrderCreationRequest(OrderCreationRequest request) {
if (request.getPlaceType() == OrderConstant.PlaceType.RANDOM
&& !request.isValidForRandomOrder()) {
throw new CustomException("随机单必须提供店员要求信息");
}
}
/**
* 构建订单实体
*/
private PlayOrderInfoEntity buildOrderEntity(OrderCreationRequest request) {
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
// 基本信息
entity.setId(request.getOrderId());
entity.setOrderNo(request.getOrderNo());
entity.setOrderStatus(request.getOrderStatus().getCode());
entity.setOrderType(request.getOrderType().getCode());
entity.setPlaceType(request.getPlaceType().getCode());
entity.setRewardType(request.getRewardType());
entity.setFirstOrder(request.getFirstOrderString());
// 固定默认值
entity.setRefundType("0");
entity.setBackendEntry("0");
entity.setPayMethod("0");
entity.setOrderSettlementState("0");
entity.setOrdersExpiredState("0");
// 商品信息
CommodityInfo commodityInfo = request.getCommodityInfo();
entity.setCommodityId(commodityInfo.getCommodityId());
entity.setCommodityType(commodityInfo.getCommodityType());
entity.setCommodityPrice(commodityInfo.getCommodityPrice());
entity.setServiceDuration(commodityInfo.getServiceDuration());
entity.setCommodityName(commodityInfo.getCommodityName());
entity.setCommodityNumber(commodityInfo.getCommodityNumber());
// 支付信息
PaymentInfo paymentInfo = request.getPaymentInfo();
entity.setOrderMoney(paymentInfo.getOrderMoney());
entity.setFinalAmount(paymentInfo.getFinalAmount());
entity.setDiscountAmount(paymentInfo.getDiscountAmount());
entity.setCouponIds(paymentInfo.getCouponIds());
entity.setUseCoupon(
paymentInfo.getCouponIds() != null && !paymentInfo.getCouponIds().isEmpty() ? "1" : "0");
// 用户信息
entity.setPurchaserBy(request.getPurchaserBy());
entity.setPurchaserTime(LocalDateTime.now());
entity.setWeiChatCode(request.getWeiChatCode());
entity.setRemark(request.getRemark());
return entity;
}
/**
* 设置随机单要求
*/
private void setRandomOrderRequirements(PlayOrderInfoEntity entity, RandomOrderRequirements requirements) {
if (requirements != null) {
entity.setSex(requirements.getClerkGender().getCode());
entity.setLevelId(requirements.getClerkLevelId());
entity.setExcludeHistory(requirements.getExcludeHistory());
}
}
/**
* 设置接单人信息
*/
private void setAcceptByInfo(PlayOrderInfoEntity entity, OrderCreationRequest request) {
entity.setAcceptBy(request.getAcceptBy());
ClerkEstimatedRevenueVo estimatedRevenueVo = getClerkEstimatedRevenue(
request.getAcceptBy(),
request.getPaymentInfo().getCouponIds(),
request.getPlaceType().getCode(),
request.getFirstOrderString(),
request.getPaymentInfo().getFinalAmount());
entity.setEstimatedRevenue(estimatedRevenueVo.getRevenueAmount());
entity.setEstimatedRevenueRatio(estimatedRevenueVo.getRevenueRatio());
}
/**
* 设置打赏单为已完成状态
*/
private void setRewardOrderCompleted(PlayOrderInfoEntity entity) {
LocalDateTime now = LocalDateTime.now();
entity.setAcceptTime(now);
entity.setOrderStartTime(now);
entity.setOrderEndTime(now);
}
@Override
public ClerkEstimatedRevenueVo getClerkEstimatedRevenue(String clerkId, List<String> croupIds, String placeType,
String firstOrder, BigDecimal finalAmount) {