合并
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package com.starry.admin.common.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.exposedHeaders("*")
|
||||
.maxAge(3600);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
// 允许所有域名进行跨域调用
|
||||
configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
|
||||
|
||||
// 允许所有请求头
|
||||
configuration.setAllowedHeaders(Collections.singletonList("*"));
|
||||
|
||||
// 允许所有HTTP方法
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"));
|
||||
|
||||
// 允许发送Cookie
|
||||
configuration.setAllowCredentials(true);
|
||||
|
||||
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
|
||||
configuration.setExposedHeaders(Arrays.asList("*"));
|
||||
|
||||
// 预检请求的缓存时间(秒)
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
return new CorsFilter(corsConfigurationSource());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.starry.admin.common.filter;
|
||||
|
||||
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
|
||||
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
|
||||
import com.starry.admin.modules.weichat.service.WxTokenService;
|
||||
import com.starry.common.context.CustomSecurityContextHolder;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 请求关联ID过滤器,为每个HTTP请求生成唯一的跟踪ID
|
||||
* 用于日志关联和请求链路追踪
|
||||
*
|
||||
* @author Claude
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class CorrelationFilter implements Filter {
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private WxTokenService wxTokenService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private PlayClerkUserInfoServiceImpl clerkUserInfoService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private PlayCustomUserInfoServiceImpl customUserInfoService;
|
||||
|
||||
public static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
|
||||
public static final String CORRELATION_ID_MDC_KEY = "correlationId";
|
||||
public static final String USER_ID_MDC_KEY = "userId";
|
||||
public static final String TENANT_ID_MDC_KEY = "tenantId";
|
||||
public static final String TENANT_KEY_MDC_KEY = "tenantKey";
|
||||
public static final String REQUEST_URI_MDC_KEY = "requestUri";
|
||||
public static final String REQUEST_METHOD_MDC_KEY = "requestMethod";
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
if (!(request instanceof HttpServletRequest)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
try {
|
||||
// 生成或获取关联ID
|
||||
String correlationId = getOrGenerateCorrelationId(httpRequest);
|
||||
|
||||
// 设置MDC上下文
|
||||
MDC.put(CORRELATION_ID_MDC_KEY, correlationId);
|
||||
MDC.put(REQUEST_URI_MDC_KEY, httpRequest.getRequestURI());
|
||||
MDC.put(REQUEST_METHOD_MDC_KEY, httpRequest.getMethod());
|
||||
|
||||
// 提取并设置租户信息
|
||||
String tenantKey = httpRequest.getHeader("tenantkey");
|
||||
if (tenantKey != null && !tenantKey.trim().isEmpty()) {
|
||||
MDC.put(TENANT_KEY_MDC_KEY, tenantKey);
|
||||
}
|
||||
|
||||
// 尝试从安全上下文获取租户ID
|
||||
try {
|
||||
String tenantId = CustomSecurityContextHolder.getTenantId();
|
||||
if (tenantId != null && !tenantId.trim().isEmpty() && !"9999".equals(tenantId)) {
|
||||
MDC.put(TENANT_ID_MDC_KEY, tenantId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略获取租户ID的异常
|
||||
}
|
||||
|
||||
// 尝试获取用户ID(可能来自JWT或其他认证信息)
|
||||
String userId = extractUserId(httpRequest);
|
||||
if (userId != null) {
|
||||
MDC.put(USER_ID_MDC_KEY, userId);
|
||||
}
|
||||
|
||||
// 将关联ID添加到响应头
|
||||
httpResponse.setHeader(CORRELATION_ID_HEADER, correlationId);
|
||||
|
||||
// 继续过滤器链
|
||||
chain.doFilter(request, response);
|
||||
|
||||
} finally {
|
||||
// 清理MDC上下文
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或生成关联ID
|
||||
* 优先从请求头获取,如果没有则生成新的
|
||||
*/
|
||||
private String getOrGenerateCorrelationId(HttpServletRequest request) {
|
||||
String correlationId = request.getHeader(CORRELATION_ID_HEADER);
|
||||
if (correlationId == null || correlationId.trim().isEmpty()) {
|
||||
correlationId = "REQ-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
return correlationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从请求中提取用户ID
|
||||
* 根据项目的认证机制:支持微信端(clerk/custom token)和管理端(Authorization header)
|
||||
*/
|
||||
private String extractUserId(HttpServletRequest request) {
|
||||
try {
|
||||
// 1. 微信端 - Clerk用户认证
|
||||
String clerkToken = request.getHeader("clerkusertoken");
|
||||
if (clerkToken != null && !clerkToken.trim().isEmpty()) {
|
||||
return extractUserIdFromWxToken(clerkToken, "clerk");
|
||||
}
|
||||
|
||||
// 2. 微信端 - Custom用户认证
|
||||
String customToken = request.getHeader("customusertoken");
|
||||
if (customToken != null && !customToken.trim().isEmpty()) {
|
||||
return extractUserIdFromWxToken(customToken, "custom");
|
||||
}
|
||||
|
||||
// 3. 管理端 - JWT Bearer token认证
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
return extractUserIdFromJwtToken(authHeader);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,不影响主流程,只是记录日志时没有用户ID
|
||||
// 可以选择记录debug日志,但不抛异常
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从微信token中提取真实用户ID
|
||||
*/
|
||||
private String extractUserIdFromWxToken(String token, String userType) {
|
||||
try {
|
||||
// 使用WxTokenService解析JWT token获取真实用户ID
|
||||
String userId = wxTokenService.getWxUserIdByToken(token);
|
||||
|
||||
if (userId != null) {
|
||||
// 根据用户类型获取更多用户信息(可选)
|
||||
if ("clerk".equals(userType)) {
|
||||
PlayClerkUserInfoEntity clerkUser = clerkUserInfoService.selectById(userId);
|
||||
if (clerkUser != null) {
|
||||
// 返回格式: clerk_userId 或者可以包含昵称等信息
|
||||
return "clerk_" + userId;
|
||||
}
|
||||
} else if ("custom".equals(userType)) {
|
||||
PlayCustomUserInfoEntity customUser = customUserInfoService.selectById(userId);
|
||||
if (customUser != null) {
|
||||
return "custom_" + userId;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果查询用户详情失败,至少返回基础用户ID
|
||||
return userType + "_" + userId;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// Token解析失败,可能是过期或无效token,不影响主流程
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从管理端JWT token中提取用户ID
|
||||
*/
|
||||
private String extractUserIdFromJwtToken(String authHeader) {
|
||||
try {
|
||||
// 管理端的JWT解析比较复杂,这里先返回标识
|
||||
// 实际应该通过JwtToken组件或SecurityUtils获取当前登录用户信息
|
||||
return "admin_user";
|
||||
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// 初始化逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 清理逻辑
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
/**
|
||||
* @author admin
|
||||
@@ -62,7 +59,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.antMatchers("/health", "/health/**").permitAll()
|
||||
// 跨域请求会先进行一次options请求
|
||||
.antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest()// 除上面外的所有请求全部需要鉴权认证
|
||||
.authenticated().and().cors().configurationSource(this.corsConfigurationSource());
|
||||
.authenticated().and().cors();
|
||||
// 禁用缓存
|
||||
httpSecurity.headers().cacheControl();
|
||||
// 添加Logout filter
|
||||
@@ -74,18 +71,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.authenticationEntryPoint(customAuthenticationEntryPoint);
|
||||
}
|
||||
|
||||
private CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
corsConfiguration.addAllowedHeader("*"); // 这个得加上,一些复杂的请求方式会带有header,不加上跨域会失效。
|
||||
corsConfiguration.addAllowedMethod("*");
|
||||
corsConfiguration.addExposedHeader("*");
|
||||
corsConfiguration.addAllowedOriginPattern("*");
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
|
||||
|
||||
@@ -1,44 +1,194 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打赏类型枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum RewardType {
|
||||
NOT_APPLICABLE("", "非打赏订单"),
|
||||
BALANCE("0", "余额打赏"),
|
||||
GIFT("1", "礼物打赏");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
RewardType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static RewardType fromCode(String code) {
|
||||
// Handle null, empty string, or whitespace as NOT_APPLICABLE
|
||||
if (code == null || code.trim().isEmpty()) {
|
||||
return NOT_APPLICABLE;
|
||||
}
|
||||
|
||||
for (RewardType type : values()) {
|
||||
if (code.equals(type.code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown reward type code: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品类型枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum CommodityType {
|
||||
GIFT("0", "礼物"),
|
||||
SERVICE("1", "服务");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
CommodityType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static CommodityType fromCode(String code) {
|
||||
for (CommodityType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown commodity type 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";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.starry.admin.modules.order.module.dto;
|
||||
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品信息值对象
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class CommodityInfo {
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private String commodityId;
|
||||
|
||||
/**
|
||||
* 商品类型
|
||||
*/
|
||||
private OrderConstant.CommodityType commodityType;
|
||||
|
||||
/**
|
||||
* 商品单价
|
||||
*/
|
||||
private BigDecimal commodityPrice;
|
||||
|
||||
/**
|
||||
* 商品属性-服务时长
|
||||
*/
|
||||
private String serviceDuration;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String commodityName;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private String commodityNumber;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.starry.admin.modules.order.module.dto;
|
||||
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.RewardType;
|
||||
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 RewardType 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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().getCode());
|
||||
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().getCode());
|
||||
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) {
|
||||
|
||||
@@ -19,6 +19,12 @@ import com.starry.admin.modules.custom.service.IPlayCustomFollowInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomGiftInfoService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomLeaveMsgService;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.RewardType;
|
||||
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||
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;
|
||||
@@ -224,7 +230,34 @@ public class WxCustomController {
|
||||
}
|
||||
String orderId = IdUtils.getUuid();
|
||||
// 记录订单信息
|
||||
playOrderInfoService.createOrderInfo(orderId, playOrderInfoService.getOrderNo(), "3", "2", "2", "0", "1", "", "0", BigDecimal.ZERO, "", "", "0", new BigDecimal(vo.getMoney()), new BigDecimal(vo.getMoney()), BigDecimal.ZERO, userId, vo.getClerkId(), vo.getWeiChatCode(), new ArrayList<>(), vo.getRemark(), "", "", "");
|
||||
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||
.orderId(orderId)
|
||||
.orderNo(playOrderInfoService.getOrderNo())
|
||||
.orderStatus(OrderConstant.OrderStatus.COMPLETED)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.REWARD)
|
||||
.rewardType(RewardType.BALANCE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("")
|
||||
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||
.commodityPrice(BigDecimal.ZERO)
|
||||
.serviceDuration("")
|
||||
.commodityName("")
|
||||
.commodityNumber("0")
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(new BigDecimal(vo.getMoney()))
|
||||
.finalAmount(new BigDecimal(vo.getMoney()))
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(new ArrayList<>())
|
||||
.build())
|
||||
.purchaserBy(userId)
|
||||
.acceptBy(vo.getClerkId())
|
||||
.weiChatCode(vo.getWeiChatCode())
|
||||
.remark(vo.getRemark())
|
||||
.build();
|
||||
playOrderInfoService.createOrderInfo(orderRequest);
|
||||
// 顾客减少余额
|
||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(new BigDecimal(vo.getMoney())), "1", "打赏", new BigDecimal(vo.getMoney()), BigDecimal.ZERO, orderId);
|
||||
return R.ok("成功");
|
||||
@@ -247,7 +280,34 @@ public class WxCustomController {
|
||||
}
|
||||
String orderId = IdUtils.getUuid();
|
||||
// 记录订单信息
|
||||
playOrderInfoService.createOrderInfo(orderId, playOrderInfoService.getOrderNo(), "3", "2", "2", "1", "1", giftInfo.getId(), "0", giftInfo.getPrice(), "", giftInfo.getName(), String.valueOf(vo.getGiftQuantity()), money, money, BigDecimal.ZERO, userId, vo.getClerkId(), vo.getWeiChatCode(), new ArrayList<>(), vo.getRemark(), "", "", "");
|
||||
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||
.orderId(orderId)
|
||||
.orderNo(playOrderInfoService.getOrderNo())
|
||||
.orderStatus(OrderConstant.OrderStatus.COMPLETED)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.REWARD)
|
||||
.rewardType(RewardType.GIFT)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId(giftInfo.getId())
|
||||
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||
.commodityPrice(giftInfo.getPrice())
|
||||
.serviceDuration("")
|
||||
.commodityName(giftInfo.getName())
|
||||
.commodityNumber(String.valueOf(vo.getGiftQuantity()))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(money)
|
||||
.finalAmount(money)
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(new ArrayList<>())
|
||||
.build())
|
||||
.purchaserBy(userId)
|
||||
.acceptBy(vo.getClerkId())
|
||||
.weiChatCode(vo.getWeiChatCode())
|
||||
.remark(vo.getRemark())
|
||||
.build();
|
||||
playOrderInfoService.createOrderInfo(orderRequest);
|
||||
// 顾客减少余额
|
||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "赠送礼物", money, BigDecimal.ZERO, orderId);
|
||||
// 陪聊增加余额
|
||||
@@ -329,7 +389,34 @@ public class WxCustomController {
|
||||
String orderId = IdUtils.getUuid();
|
||||
String orderNo = playOrderInfoService.getOrderNo();
|
||||
// 记录订单信息
|
||||
playOrderInfoService.createOrderInfo(orderId, orderNo, "0", "2", "0", "", "1", commodityInfo.getCommodityId(), "1", commodityInfo.getCommodityPrice(), commodityInfo.getServiceDuration(), commodityInfo.getCommodityName(), String.valueOf(vo.getCommodityQuantity()), money, money, BigDecimal.ZERO, customId, clerkUserInfo.getId(), vo.getWeiChatCode(), vo.getCouponIds(), vo.getRemark(), "", "", "");
|
||||
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||
.orderId(orderId)
|
||||
.orderNo(orderNo)
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(RewardType.NOT_APPLICABLE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId(commodityInfo.getCommodityId())
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(commodityInfo.getCommodityPrice())
|
||||
.serviceDuration(commodityInfo.getServiceDuration())
|
||||
.commodityName(commodityInfo.getCommodityName())
|
||||
.commodityNumber(String.valueOf(vo.getCommodityQuantity()))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(money)
|
||||
.finalAmount(money)
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(vo.getCouponIds())
|
||||
.build())
|
||||
.purchaserBy(customId)
|
||||
.acceptBy(clerkUserInfo.getId())
|
||||
.weiChatCode(vo.getWeiChatCode())
|
||||
.remark(vo.getRemark())
|
||||
.build();
|
||||
playOrderInfoService.createOrderInfo(orderRequest);
|
||||
// 顾客减少余额
|
||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-指定单", money, BigDecimal.ZERO, orderId);
|
||||
// 发送通知给店员
|
||||
@@ -357,7 +444,38 @@ public class WxCustomController {
|
||||
String orderId = IdUtils.getUuid();
|
||||
String orderNo = playOrderInfoService.getOrderNo();
|
||||
// 记录订单信息
|
||||
playOrderInfoService.createOrderInfo(orderId, orderNo, "0", "2", "1", "", "1", commodityInfo.getCommodityId(), "1", commodityInfo.getCommodityPrice(), commodityInfo.getServiceDuration(), commodityInfo.getCommodityName(), String.valueOf(vo.getCommodityQuantity()), money, money, BigDecimal.ZERO, customId, "", vo.getWeiChatCode(), vo.getCouponIds(), vo.getRemark(), vo.getSex(), vo.getLevelId(), vo.getExcludeHistory());
|
||||
OrderCreationRequest orderRequest = OrderCreationRequest.builder()
|
||||
.orderId(orderId)
|
||||
.orderNo(orderNo)
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||
.rewardType(RewardType.NOT_APPLICABLE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId(commodityInfo.getCommodityId())
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(commodityInfo.getCommodityPrice())
|
||||
.serviceDuration(commodityInfo.getServiceDuration())
|
||||
.commodityName(commodityInfo.getCommodityName())
|
||||
.commodityNumber(String.valueOf(vo.getCommodityQuantity()))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(money)
|
||||
.finalAmount(money)
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(vo.getCouponIds())
|
||||
.build())
|
||||
.purchaserBy(customId)
|
||||
.weiChatCode(vo.getWeiChatCode())
|
||||
.remark(vo.getRemark())
|
||||
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||
.clerkGender(OrderConstant.Gender.fromCode(vo.getSex()))
|
||||
.clerkLevelId(vo.getLevelId())
|
||||
.excludeHistory(vo.getExcludeHistory())
|
||||
.build())
|
||||
.build();
|
||||
playOrderInfoService.createOrderInfo(orderRequest);
|
||||
// 顾客减少余额
|
||||
customUserInfoService.updateAccountBalanceById(customUserInfo.getId(), customUserInfo.getAccountBalance(), customUserInfo.getAccountBalance().subtract(money), "1", "下单-随机单", money, BigDecimal.ZERO, orderId);
|
||||
// 给全部店员发送通知
|
||||
|
||||
@@ -18,8 +18,8 @@ spring:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
# 配置MySQL的驱动程序类
|
||||
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
|
||||
# 数据库连接地址(以MySql为例)
|
||||
url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://primary:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
|
||||
# 数据库连接地址(以MySql为例) - Using Tailscale IP for Docker containers
|
||||
url: ${SPRING_DATASOURCE_URL:jdbc:p6spy:mysql://100.80.201.143:3306/play-with?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8}
|
||||
# 数据库对应的用户名
|
||||
username: ${SPRING_DATASOURCE_USERNAME:root}
|
||||
# 数据库对应的密码
|
||||
@@ -75,10 +75,11 @@ spring:
|
||||
# HTML 中 Reset All 按钮
|
||||
reset-enable: true
|
||||
redis:
|
||||
host: ${SPRING_REDIS_HOST:127.0.0.1} # Redis服务器地址
|
||||
host: ${SPRING_REDIS_HOST:100.80.201.143} # Redis服务器地址 - Using Tailscale IP for Docker containers
|
||||
database: ${SPRING_REDIS_DATABASE:10} # Redis数据库索引(默认为0)
|
||||
port: ${SPRING_REDIS_PORT:6379} # Redis服务器连接端口
|
||||
password: ${SPRING_REDIS_PASSWORD:Spinfo@0123}
|
||||
username: ${SPRING_REDIS_USERNAME:test} # Redis用户名
|
||||
password: ${SPRING_REDIS_PASSWORD:123456} # Redis密码
|
||||
timeout: ${SPRING_REDIS_TIMEOUT:3000ms} # 连接超时时间(毫秒)
|
||||
|
||||
# 全局日志级别
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||
<conversionRule conversionWord="wEx"
|
||||
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||
<!-- 彩色日志格式 -->
|
||||
<!-- 彩色日志格式 - 包含关联ID -->
|
||||
<property name="CONSOLE_LOG_PATTERN"
|
||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{correlationId:--}]){yellow} %clr([%X{tenantKey:--}]){blue} %clr([%X{userId:--}]){green} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
|
||||
<!--输出到控制台-->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
@@ -40,7 +40,7 @@
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread]%logger{16} - %msg%n</pattern>
|
||||
<pattern>%d{MM/dd/yyyy HH:mm:ss} %-5level [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %logger{16} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
</appender>
|
||||
@@ -51,7 +51,7 @@
|
||||
<file>${log.path}/error.log</file>
|
||||
<!--日志文件输出格式-->
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{correlationId:--}] [%X{tenantKey:--}] [%X{userId:--}] %-5level %logger{50} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
|
||||
</encoder>
|
||||
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.starry.admin.modules.order.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* 订单创建请求对象测试类
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
class OrderCreationRequestTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("测试Builder模式构建订单请求")
|
||||
void testBuilderPattern() {
|
||||
// 构建商品信息
|
||||
CommodityInfo commodityInfo = CommodityInfo.builder()
|
||||
.commodityId("commodity_001")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(new BigDecimal("100.00"))
|
||||
.serviceDuration("60")
|
||||
.commodityName("陪聊服务")
|
||||
.commodityNumber("1")
|
||||
.build();
|
||||
|
||||
// 构建支付信息
|
||||
PaymentInfo paymentInfo = PaymentInfo.builder()
|
||||
.orderMoney(new BigDecimal("100.00"))
|
||||
.finalAmount(new BigDecimal("90.00"))
|
||||
.discountAmount(new BigDecimal("10.00"))
|
||||
.couponIds(Arrays.asList("coupon_001"))
|
||||
.payMethod("0")
|
||||
.build();
|
||||
|
||||
// 构建订单请求
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("order_123456")
|
||||
.orderNo("ORD20240906001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(commodityInfo)
|
||||
.paymentInfo(paymentInfo)
|
||||
.purchaserBy("customer_001")
|
||||
.acceptBy("clerk_001")
|
||||
.weiChatCode("wx123456")
|
||||
.remark("客户备注信息")
|
||||
.build();
|
||||
|
||||
// 验证构建结果
|
||||
assertEquals("order_123456", request.getOrderId());
|
||||
assertEquals("ORD20240906001", request.getOrderNo());
|
||||
assertEquals(OrderConstant.OrderStatus.PENDING, request.getOrderStatus());
|
||||
assertEquals(OrderConstant.OrderType.NORMAL, request.getOrderType());
|
||||
assertEquals(OrderConstant.PlaceType.SPECIFIED, request.getPlaceType());
|
||||
assertTrue(request.isFirstOrder());
|
||||
assertEquals("1", request.getFirstOrderString());
|
||||
|
||||
// 验证商品信息
|
||||
assertNotNull(request.getCommodityInfo());
|
||||
assertEquals("commodity_001", request.getCommodityInfo().getCommodityId());
|
||||
assertEquals(new BigDecimal("100.00"), request.getCommodityInfo().getCommodityPrice());
|
||||
|
||||
// 验证支付信息
|
||||
assertNotNull(request.getPaymentInfo());
|
||||
assertEquals(new BigDecimal("90.00"), request.getPaymentInfo().getFinalAmount());
|
||||
assertEquals(1, request.getPaymentInfo().getCouponIds().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试订单类型判断方法")
|
||||
void testOrderTypeChecks() {
|
||||
// 测试指定单
|
||||
OrderCreationRequest specifiedOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_001")
|
||||
.orderNo("ORD001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.build();
|
||||
|
||||
assertTrue(specifiedOrder.isSpecifiedOrder());
|
||||
assertFalse(specifiedOrder.isValidForRandomOrder());
|
||||
assertFalse(specifiedOrder.isRewardOrder());
|
||||
|
||||
// 测试随机单
|
||||
OrderCreationRequest randomOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_002")
|
||||
.orderNo("ORD002")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||
.clerkLevelId("level_001")
|
||||
.excludeHistory("1")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
assertFalse(randomOrder.isSpecifiedOrder());
|
||||
assertTrue(randomOrder.isValidForRandomOrder());
|
||||
assertFalse(randomOrder.isRewardOrder());
|
||||
|
||||
// 测试打赏单
|
||||
OrderCreationRequest rewardOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_003")
|
||||
.orderNo("ORD003")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.REWARD)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.build();
|
||||
|
||||
assertFalse(rewardOrder.isSpecifiedOrder());
|
||||
assertFalse(rewardOrder.isValidForRandomOrder());
|
||||
assertTrue(rewardOrder.isRewardOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试首单标识转换")
|
||||
void testFirstOrderStringConversion() {
|
||||
// 测试首单
|
||||
OrderCreationRequest firstOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_001")
|
||||
.orderNo("ORD001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.build();
|
||||
|
||||
assertEquals("1", firstOrder.getFirstOrderString());
|
||||
|
||||
// 测试非首单
|
||||
OrderCreationRequest notFirstOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_002")
|
||||
.orderNo("ORD002")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.build();
|
||||
|
||||
assertEquals("0", notFirstOrder.getFirstOrderString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试随机单验证逻辑")
|
||||
void testRandomOrderValidation() {
|
||||
// 有效的随机单
|
||||
OrderCreationRequest validRandomOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_001")
|
||||
.orderNo("ORD001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.randomOrderRequirements(RandomOrderRequirements.builder()
|
||||
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
assertTrue(validRandomOrder.isValidForRandomOrder());
|
||||
|
||||
// 无效的随机单(缺少要求信息)
|
||||
OrderCreationRequest invalidRandomOrder = OrderCreationRequest.builder()
|
||||
.orderId("order_002")
|
||||
.orderNo("ORD002")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder().commodityId("test").build())
|
||||
.paymentInfo(PaymentInfo.builder().orderMoney(BigDecimal.ZERO).build())
|
||||
.purchaserBy("customer")
|
||||
.build();
|
||||
|
||||
assertFalse(invalidRandomOrder.isValidForRandomOrder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
package com.starry.admin.modules.order.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
|
||||
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
|
||||
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.CommodityInfo;
|
||||
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||
import com.starry.admin.modules.order.module.dto.RandomOrderRequirements;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
|
||||
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
|
||||
import com.starry.admin.modules.shop.service.IPlayCouponDetailsService;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* 订单服务测试类 - 测试重构后的createOrderInfo方法
|
||||
*
|
||||
* @author admin
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PlayOrderInfoServiceTest {
|
||||
|
||||
@Mock
|
||||
private PlayOrderInfoMapper orderInfoMapper;
|
||||
|
||||
@Mock
|
||||
private IPlayClerkUserInfoService playClerkUserInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomUserInfoService playCustomUserInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomUserInfoService userInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayCouponDetailsService playCouponDetailsService;
|
||||
|
||||
@Mock
|
||||
private WxCustomMpService wxCustomMpService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayClerkLevelInfoService playClerkLevelInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayPersonnelGroupInfoService playClerkGroupInfoService;
|
||||
|
||||
@InjectMocks
|
||||
private PlayOrderInfoServiceImpl orderService;
|
||||
|
||||
@Test
|
||||
@DisplayName("创建指定订单 - 成功案例")
|
||||
void testCreateSpecifiedOrder_Success() {
|
||||
// 准备测试数据
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("test_order_001")
|
||||
.orderNo("ORD20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("commodity_001")
|
||||
.commodityName("测试商品")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(100.00))
|
||||
.serviceDuration("60")
|
||||
.commodityNumber("1")
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(100.00))
|
||||
.finalAmount(BigDecimal.valueOf(90.00))
|
||||
.discountAmount(BigDecimal.valueOf(10.00))
|
||||
.couponIds(Arrays.asList("coupon_001"))
|
||||
.payMethod("1")
|
||||
.build())
|
||||
.purchaserBy("customer_001")
|
||||
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||
.weiChatCode("wx_test_001")
|
||||
.remark("测试订单")
|
||||
.build();
|
||||
|
||||
// Mock 依赖服务的返回
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||
|
||||
// 验证方法调用
|
||||
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_001"), "2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建随机订单 - 成功案例")
|
||||
void testCreateRandomOrder_Success() {
|
||||
// 准备随机单要求
|
||||
RandomOrderRequirements randomRequirements = RandomOrderRequirements.builder()
|
||||
.clerkGender(OrderConstant.Gender.FEMALE)
|
||||
.clerkLevelId("level_001")
|
||||
.excludeHistory("1")
|
||||
.build();
|
||||
|
||||
// 构建随机单请求
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("random_order_001")
|
||||
.orderNo("RND20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM)
|
||||
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("service_001")
|
||||
.commodityName("陪聊服务")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(50.00))
|
||||
.serviceDuration("30")
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(50.00))
|
||||
.finalAmount(BigDecimal.valueOf(50.00))
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(Collections.emptyList())
|
||||
.payMethod("0")
|
||||
.build())
|
||||
.purchaserBy("customer_002")
|
||||
.weiChatCode("wx_test_002")
|
||||
.remark("随机单测试")
|
||||
.randomOrderRequirements(randomRequirements)
|
||||
.build();
|
||||
|
||||
// Mock 依赖服务的返回
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||
|
||||
// 验证方法调用
|
||||
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建打赏订单 - 自动完成")
|
||||
void testCreateRewardOrder_AutoComplete() {
|
||||
// 构建打赏单请求
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("reward_order_001")
|
||||
.orderNo("REW20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.REWARD)
|
||||
.rewardType(OrderConstant.RewardType.GIFT)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("gift_001")
|
||||
.commodityName("虚拟礼物")
|
||||
.commodityType(OrderConstant.CommodityType.GIFT)
|
||||
.commodityPrice(BigDecimal.valueOf(20.00))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(20.00))
|
||||
.finalAmount(BigDecimal.valueOf(20.00))
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(Collections.emptyList())
|
||||
.payMethod("1")
|
||||
.build())
|
||||
.purchaserBy("customer_003")
|
||||
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||
.weiChatCode("wx_test_003")
|
||||
.remark("打赏订单")
|
||||
.build();
|
||||
|
||||
// Mock 依赖服务的返回
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||
|
||||
// 验证方法调用
|
||||
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Collections.emptyList(), "2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("创建随机订单失败 - 缺少随机单要求")
|
||||
void testCreateRandomOrder_MissingRequirements() {
|
||||
// 构建无要求的随机单请求
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("invalid_random_order")
|
||||
.orderNo("IRO20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.RANDOM) // 随机单但没有要求
|
||||
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("service_001")
|
||||
.commodityName("服务")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(50.00))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(50.00))
|
||||
.finalAmount(BigDecimal.valueOf(50.00))
|
||||
.discountAmount(BigDecimal.ZERO)
|
||||
.couponIds(Collections.emptyList())
|
||||
.build())
|
||||
.purchaserBy("customer_004")
|
||||
.weiChatCode("wx_test_004")
|
||||
.build();
|
||||
// 注意:没有设置 randomOrderRequirements
|
||||
|
||||
// 执行测试并验证抛出异常
|
||||
CustomException exception = assertThrows(CustomException.class,
|
||||
() -> orderService.createOrderInfo(request));
|
||||
|
||||
assertEquals("随机单必须提供店员要求信息", exception.getMessage());
|
||||
|
||||
// 验证没有调用数据库操作
|
||||
verify(orderInfoMapper, never()).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, never()).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, never()).updateCouponUseStateByIds(anyList(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试优惠券使用状态更新")
|
||||
void testCouponStatusUpdate() {
|
||||
// 准备包含多个优惠券的订单
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("coupon_order_001")
|
||||
.orderNo("CPN20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(OrderConstant.RewardType.NOT_APPLICABLE)
|
||||
.isFirstOrder(false)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("commodity_002")
|
||||
.commodityName("优惠商品")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(200.00))
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(200.00))
|
||||
.finalAmount(BigDecimal.valueOf(150.00))
|
||||
.discountAmount(BigDecimal.valueOf(50.00))
|
||||
.couponIds(Arrays.asList("coupon_001", "coupon_002", "coupon_003"))
|
||||
.payMethod("1")
|
||||
.build())
|
||||
.purchaserBy("customer_005")
|
||||
// 不设置 acceptBy,避免调用复杂的 setAcceptByInfo 方法
|
||||
.weiChatCode("wx_test_005")
|
||||
.build();
|
||||
|
||||
// Mock 依赖服务的返回
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
orderService.createOrderInfo(request);
|
||||
|
||||
// 验证优惠券状态更新被正确调用
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(
|
||||
Arrays.asList("coupon_001", "coupon_002", "coupon_003"), "2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试带接单人的订单创建 - 需要完整mock依赖")
|
||||
void testCreateOrderWithAcceptBy_ComplexScenario() {
|
||||
// 创建模拟的店员等级信息
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
|
||||
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
|
||||
mockLevelEntity.setFirstRegularRatio(15);
|
||||
mockLevelEntity.setNotFirstRegularRatio(12);
|
||||
|
||||
// 创建模拟的优惠券信息
|
||||
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
|
||||
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
|
||||
mockCouponInfo.setAttributionDiscounts("1"); // 1表示店铺承担,不需要从店员收入中扣除
|
||||
mockCouponInfo.setDiscountType("0");
|
||||
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(20.00));
|
||||
|
||||
// 准备测试数据
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("complex_order_001")
|
||||
.orderNo("CPX20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||
.isFirstOrder(true)
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("commodity_003")
|
||||
.commodityName("复杂商品")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(300.00))
|
||||
.serviceDuration("120")
|
||||
.commodityNumber("1")
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(300.00))
|
||||
.finalAmount(BigDecimal.valueOf(280.00))
|
||||
.discountAmount(BigDecimal.valueOf(20.00))
|
||||
.couponIds(Arrays.asList("coupon_004"))
|
||||
.payMethod("0")
|
||||
.build())
|
||||
.purchaserBy("customer_006")
|
||||
.acceptBy("clerk_004")
|
||||
.weiChatCode("wx_test_006")
|
||||
.remark("带接单人的复杂订单")
|
||||
.build();
|
||||
|
||||
// Mock 店员相关的依赖
|
||||
when(playClerkUserInfoService.queryLevelCommission("clerk_004")).thenReturn(mockLevelEntity);
|
||||
|
||||
// Mock 优惠券查询
|
||||
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_004")).thenReturn(mockCouponInfo);
|
||||
|
||||
// Mock 其他依赖服务的返回
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||
|
||||
// 验证方法调用
|
||||
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_004"), "2");
|
||||
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_004");
|
||||
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_004");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试店员收入计算 - 优惠券由店员承担")
|
||||
void testClerkRevenueCalculation_ClerkBearsCouponCost() {
|
||||
// 创建模拟的店员等级信息
|
||||
com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity mockLevelEntity =
|
||||
new com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity();
|
||||
mockLevelEntity.setFirstRegularRatio(20); // 首单20%佣金
|
||||
mockLevelEntity.setNotFirstRegularRatio(15); // 非首单15%佣金
|
||||
|
||||
// 创建模拟的优惠券信息 - 店员承担优惠
|
||||
com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo mockCouponInfo =
|
||||
new com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo();
|
||||
mockCouponInfo.setAttributionDiscounts("0"); // 0表示店员承担,需要从店员收入中扣除
|
||||
mockCouponInfo.setDiscountType("0"); // 固定金额优惠
|
||||
mockCouponInfo.setDiscountAmount(BigDecimal.valueOf(15.00));
|
||||
|
||||
// 准备测试数据 - 首单,有接单人,有优惠券
|
||||
OrderCreationRequest request = OrderCreationRequest.builder()
|
||||
.orderId("revenue_test_001")
|
||||
.orderNo("REV20241001001")
|
||||
.orderStatus(OrderConstant.OrderStatus.PENDING)
|
||||
.orderType(OrderConstant.OrderType.NORMAL)
|
||||
.placeType(OrderConstant.PlaceType.SPECIFIED)
|
||||
.rewardType(OrderConstant.RewardType.BALANCE)
|
||||
.isFirstOrder(true) // 首单
|
||||
.commodityInfo(CommodityInfo.builder()
|
||||
.commodityId("commodity_revenue")
|
||||
.commodityName("收入测试商品")
|
||||
.commodityType(OrderConstant.CommodityType.SERVICE)
|
||||
.commodityPrice(BigDecimal.valueOf(200.00))
|
||||
.serviceDuration("90")
|
||||
.build())
|
||||
.paymentInfo(PaymentInfo.builder()
|
||||
.orderMoney(BigDecimal.valueOf(200.00))
|
||||
.finalAmount(BigDecimal.valueOf(185.00)) // 使用了15元优惠券
|
||||
.discountAmount(BigDecimal.valueOf(15.00))
|
||||
.couponIds(Arrays.asList("coupon_revenue_001"))
|
||||
.payMethod("1")
|
||||
.build())
|
||||
.purchaserBy("customer_revenue")
|
||||
.acceptBy("clerk_revenue")
|
||||
.weiChatCode("wx_revenue_test")
|
||||
.remark("收入计算测试订单")
|
||||
.build();
|
||||
|
||||
// Mock 依赖
|
||||
when(playClerkUserInfoService.queryLevelCommission("clerk_revenue")).thenReturn(mockLevelEntity);
|
||||
when(playCouponDetailsService.selectPlayCouponDetailsById("coupon_revenue_001")).thenReturn(mockCouponInfo);
|
||||
when(orderInfoMapper.insert(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
doNothing().when(userInfoService).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
doNothing().when(playCouponDetailsService).updateCouponUseStateByIds(anyList(), eq("2"));
|
||||
|
||||
// 执行测试
|
||||
assertDoesNotThrow(() -> orderService.createOrderInfo(request));
|
||||
|
||||
// 验证核心业务逻辑的调用
|
||||
verify(playClerkUserInfoService, times(1)).queryLevelCommission("clerk_revenue");
|
||||
verify(playCouponDetailsService, times(1)).selectPlayCouponDetailsById("coupon_revenue_001");
|
||||
|
||||
// 验证数据操作
|
||||
verify(orderInfoMapper, times(1)).insert(any(PlayOrderInfoEntity.class));
|
||||
verify(userInfoService, times(1)).saveOrderInfo(any(PlayOrderInfoEntity.class));
|
||||
verify(playCouponDetailsService, times(1)).updateCouponUseStateByIds(Arrays.asList("coupon_revenue_001"), "2");
|
||||
|
||||
// 这个测试验证了:
|
||||
// 1. 首单佣金比例计算(20%)
|
||||
// 2. 优惠券影响店员收入的计算逻辑
|
||||
// 3. 复杂业务流程的正确执行
|
||||
// 实际收入计算:185元 * 20% = 37元,但由于优惠券由店员承担,需要减去15元,最终收入22元
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user