diff --git a/mt-pay/ARCHITECTURE_COMPLETE.md b/mt-pay/ARCHITECTURE_COMPLETE.md new file mode 100644 index 0000000..df8b939 --- /dev/null +++ b/mt-pay/ARCHITECTURE_COMPLETE.md @@ -0,0 +1,179 @@ +# 系统架构完整性说明 + +## 后端架构(mt-pay) + +### 1. 包结构 + +``` +com.mtkj.mtpay/ +├── common/ # 通用组件 +│ ├── Result.java # 统一响应结果类 +│ ├── ResultCode.java # 响应码枚举 +│ ├── constants/ # 常量类 +│ │ └── PaymentConstants.java +│ └── enums/ # 枚举类 +│ ├── OrderStatus.java +│ ├── PaymentType.java +│ └── RecordType.java +├── config/ # 配置类 +│ ├── DruidDataSourceConfig.java +│ ├── MyBatisPlusConfig.java +│ ├── MyMetaObjectHandler.java +│ ├── PingPongProperties.java +│ ├── RestClientConfig.java +│ └── WebConfig.java # Web配置(跨域等) +├── controller/ # 控制器层 +│ ├── PaymentController.java +│ └── CallbackController.java +├── dto/ # 数据传输对象 +│ ├── request/ +│ ├── response/ +│ └── risk/ +├── entity/ # 实体类 +│ ├── PaymentOrder.java +│ └── PaymentRecord.java +├── exception/ # 异常处理 +│ ├── BusinessException.java +│ └── GlobalExceptionHandler.java +├── mapper/ # 数据访问层 +│ ├── PaymentOrderMapper.java +│ └── PaymentRecordMapper.java +├── service/ # 服务层(接口) +│ ├── SignatureService.java +│ ├── PingPongPayService.java +│ ├── PaymentOrderService.java +│ └── CallbackService.java +├── service/impl/ # 服务层(实现) +│ ├── SignatureServiceImpl.java +│ ├── PingPongPayServiceImpl.java +│ ├── PaymentOrderServiceImpl.java +│ └── CallbackServiceImpl.java +└── util/ # 工具类 + ├── DateUtils.java + ├── StringUtils.java + └── OrderIdGenerator.java +``` + +### 2. 核心组件说明 + +#### 统一响应格式 +- **Result**: 统一响应结果类,所有接口返回统一格式 +- **ResultCode**: 响应码枚举,定义所有业务响应码 + +#### 异常处理 +- **BusinessException**: 业务异常类 +- **GlobalExceptionHandler**: 全局异常处理器,统一处理异常 + +#### 枚举类 +- **OrderStatus**: 订单状态枚举 +- **PaymentType**: 支付类型枚举 +- **RecordType**: 记录类型枚举 + +#### 工具类 +- **DateUtils**: 日期时间工具类 +- **StringUtils**: 字符串工具类 +- **OrderIdGenerator**: 订单号生成器 + +#### 常量类 +- **PaymentConstants**: 支付相关常量 + +#### 配置类 +- **WebConfig**: Web配置(跨域、拦截器等) + +## 前端架构(MTKJPAY-FRONT) + +### 1. 目录结构 + +``` +MTKJPAY-FRONT/ +├── src/ +│ ├── api/ # API接口 +│ │ ├── request.js # Axios封装 +│ │ └── payment.js # 支付相关API +│ ├── components/ # 通用组件 +│ │ ├── PageHeader.vue +│ │ └── Loading.vue +│ ├── config/ # 配置文件 +│ │ └── index.js +│ ├── router/ # 路由配置 +│ │ └── index.js +│ ├── store/ # 状态管理 +│ │ └── index.js +│ ├── utils/ # 工具函数 +│ │ ├── constants.js # 常量定义 +│ │ ├── helpers.js # 工具函数 +│ │ └── request.js # 请求工具 +│ ├── views/ # 页面组件 +│ │ ├── CreateOrder.vue +│ │ ├── Checkout.vue +│ │ ├── PaymentResult.vue +│ │ └── OrderQuery.vue +│ ├── App.vue # 根组件 +│ └── main.js # 入口文件 +├── .env.development # 开发环境配置 +├── .env.production # 生产环境配置 +└── vite.config.js # Vite配置 +``` + +### 2. 核心组件说明 + +#### 工具类 +- **constants.js**: 常量定义(订单状态、支付类型、币种等) +- **helpers.js**: 工具函数(格式化、验证、防抖节流等) +- **request.js**: 请求工具函数 + +#### 通用组件 +- **PageHeader.vue**: 页面头部组件 +- **Loading.vue**: 加载组件 + +#### 配置管理 +- **config/index.js**: 统一配置管理 +- **.env.development**: 开发环境配置 +- **.env.production**: 生产环境配置 + +#### 状态管理 +- **store/index.js**: 简单状态管理(可扩展为Pinia) + +## 架构特点 + +### 1. 分层清晰 +- Controller → Service → Mapper → Entity +- 接口与实现分离 +- DTO与Entity分离 + +### 2. 统一规范 +- 统一响应格式(Result) +- 统一异常处理 +- 统一响应码 + +### 3. 可扩展性 +- 枚举类便于扩展 +- 工具类可复用 +- 组件化设计 + +### 4. 可维护性 +- 代码结构清晰 +- 命名规范统一 +- 注释完整 + +### 5. 完整性 +- 包含工具类、常量、枚举 +- 包含异常处理、统一响应 +- 包含配置管理、状态管理 + +## 最佳实践 + +1. **统一响应格式**: 所有接口使用Result返回 +2. **异常处理**: 使用BusinessException抛出业务异常 +3. **枚举使用**: 使用枚举替代魔法字符串 +4. **工具类**: 通用功能封装为工具类 +5. **配置管理**: 配置统一管理,支持多环境 + +## 扩展建议 + +1. **日志切面**: 添加AOP日志切面 +2. **接口文档**: 集成Swagger/OpenAPI +3. **缓存**: 添加Redis缓存支持 +4. **消息队列**: 添加消息队列支持 +5. **监控**: 添加系统监控和链路追踪 + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/MtPayApplication.java b/mt-pay/src/main/java/com/mtkj/mtpay/MtPayApplication.java index e168b73..c393579 100644 --- a/mt-pay/src/main/java/com/mtkj/mtpay/MtPayApplication.java +++ b/mt-pay/src/main/java/com/mtkj/mtpay/MtPayApplication.java @@ -1,13 +1,42 @@ package com.mtkj.mtpay; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.env.Environment; +@Slf4j @SpringBootApplication public class MtPayApplication { public static void main(String[] args) { - SpringApplication.run(MtPayApplication.class, args); + try { + SpringApplication app = new SpringApplication(MtPayApplication.class); + Environment env = app.run(args).getEnvironment(); + + String applicationName = env.getProperty("spring.application.name", "mt-pay"); + String serverPort = env.getProperty("server.port", "8080"); + String contextPath = env.getProperty("server.servlet.context-path", ""); + String activeProfiles = String.join(",", env.getActiveProfiles()); + + log.info(""" + + ======================================== + 应用启动成功! + ======================================== + 应用名称: {} + 运行环境: {} + 访问地址: http://localhost:{}{} + ======================================== + """, + applicationName, + activeProfiles.isEmpty() ? "default" : activeProfiles, + serverPort, + contextPath); + } catch (Exception e) { + log.error("应用启动失败", e); + throw e; + } } } diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/config/AliyunOSSProperties.java b/mt-pay/src/main/java/com/mtkj/mtpay/config/AliyunOSSProperties.java new file mode 100644 index 0000000..610538c --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/config/AliyunOSSProperties.java @@ -0,0 +1,35 @@ +package com.mtkj.mtpay.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 阿里云OSS配置属性 + */ +@Data +@Component +@ConfigurationProperties(prefix = "aliyun.oss") +public class AliyunOSSProperties { + + /** + * AccessKeyId + */ + private String accessId; + + /** + * AccessKeySecret + */ + private String accessKey; + + /** + * Endpoint(地域节点) + */ + private String endpoint; + + /** + * Bucket名称 + */ + private String bucketName; +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreateProductRequestDTO.java b/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreateProductRequestDTO.java new file mode 100644 index 0000000..b492640 --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/dto/request/CreateProductRequestDTO.java @@ -0,0 +1,123 @@ +package com.mtkj.mtpay.dto.request; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 创建商品请求DTO + */ +@Data +public class CreateProductRequestDTO implements Serializable { + + /** + * 商品名称 + */ + @NotBlank(message = "商品名称不能为空") + @Size(max = 255, message = "商品名称长度不能超过255") + private String name; + + /** + * 商品价格(基础价格) + */ + @NotNull(message = "商品价格不能为空") + private BigDecimal price; + + /** + * 主图URL + */ + @Size(max = 4000, message = "主图URL长度不能超过4000") + private String mainImage; + + /** + * 商品状态:ACTIVE-上架,INACTIVE-下架 + */ + private String status = "ACTIVE"; + + /** + * 店铺ID + */ + @NotNull(message = "店铺ID不能为空") + private Long shopId; + + /** + * SKU列表 + */ + @Valid + @NotNull(message = "SKU列表不能为空") + @Size(min = 1, message = "至少需要一个SKU") + private List skus; + + /** + * SKU DTO + */ + @Data + public static class CreateProductSkuDTO implements Serializable { + + /** + * SKU编码 + */ + @NotBlank(message = "SKU编码不能为空") + @Size(max = 2000, message = "SKU编码长度不能超过2000") + private String sku; + + /** + * 价格 + */ + @NotNull(message = "SKU价格不能为空") + private BigDecimal price; + + /** + * 货币(ISO 4217三位币种代码) + */ + @NotBlank(message = "货币不能为空") + @Size(min = 3, max = 3, message = "货币必须为3位ISO 4217代码") + private String currency = "USD"; + + /** + * 库存数量 + */ + @NotNull(message = "库存数量不能为空") + private Integer stock = 0; + + /** + * 销售属性(JSON格式) + */ + private String salesAttrs; + + /** + * SKU图片URL + */ + @Size(max = 4000, message = "SKU图片URL长度不能超过4000") + private String skuImage; + + /** + * 重量(单位:克) + */ + private BigDecimal weight; + + /** + * 大小/尺寸(JSON格式) + */ + @Size(max = 200, message = "尺寸JSON长度不能超过200") + private String size; + + /** + * 规格(文本描述) + */ + @Size(max = 2000, message = "规格长度不能超过2000") + private String specification; + + /** + * SKU状态:ACTIVE-启用,INACTIVE-禁用 + */ + private String status = "ACTIVE"; + } +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/CustomerDTO.java b/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/CustomerDTO.java new file mode 100644 index 0000000..2f90acd --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/CustomerDTO.java @@ -0,0 +1,71 @@ +package com.mtkj.mtpay.dto.risk; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 客户信息DTO + */ +@Data +public class CustomerDTO implements Serializable { + + private String customerId; + + private String firstName; + + private String lastName; + + private String email; + + private String domain; + + private String phone; + + private String mobile; + + private String workPhone; + + private String identificationType; + + private String identificationId; + + private String registerTime; + + private String registerIp; + + private String registerTerminal; + + private String registerCountry; + + private String registerRange; + + private String orderTime; + + private String orderIp; + + private String orderCountry; + + private String payIp; + + private String payCountry; + + private String loginTime; + + private String loginIp; + + private String lastPayTime; + + private String acquisitionChannel; + + private String firstOrder; + + private String nonMemberOrder; + + private String preferentialOrder; + + private String birthDate; + + private String customerStatus; +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/GoodsDTO.java b/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/GoodsDTO.java new file mode 100644 index 0000000..abf51d0 --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/dto/risk/GoodsDTO.java @@ -0,0 +1,27 @@ +package com.mtkj.mtpay.dto.risk; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 商品信息DTO + */ +@Data +public class GoodsDTO implements Serializable { + + private String name; + + private String description; + + private String sku; + + private String averageUnitPrice; + + private String number; + + private String virtualProduct; + + private String imgUrl; +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/exception/BusinessException.java b/mt-pay/src/main/java/com/mtkj/mtpay/exception/BusinessException.java new file mode 100644 index 0000000..1834f7f --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/exception/BusinessException.java @@ -0,0 +1,34 @@ +package com.mtkj.mtpay.exception; + +import com.mtkj.mtpay.common.ResultCode; +import lombok.Getter; + +/** + * 业务异常 + */ +@Getter +public class BusinessException extends RuntimeException { + + private final String code; + + public BusinessException(String message) { + super(message); + this.code = ResultCode.FAIL.getCode(); + } + + public BusinessException(String code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ResultCode resultCode) { + super(resultCode.getMessage()); + this.code = resultCode.getCode(); + } + + public BusinessException(ResultCode resultCode, String message) { + super(message); + this.code = resultCode.getCode(); + } +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/exception/GlobalExceptionHandler.java b/mt-pay/src/main/java/com/mtkj/mtpay/exception/GlobalExceptionHandler.java index 2d9f40d..a47649b 100644 --- a/mt-pay/src/main/java/com/mtkj/mtpay/exception/GlobalExceptionHandler.java +++ b/mt-pay/src/main/java/com/mtkj/mtpay/exception/GlobalExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import java.util.HashMap; import java.util.Map; @@ -45,6 +46,17 @@ public class GlobalExceptionHandler { .body(Result.fail(ResultCode.VALIDATION_ERROR.getCode(), ResultCode.VALIDATION_ERROR.getMessage(), errors)); } + /** + * 处理文件上传大小超限异常 + */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) { + log.warn("文件上传大小超限: {}", e.getMessage()); + String message = "文件大小超过限制,单个文件最大10MB,请压缩后重试"; + return ResponseEntity.badRequest() + .body(Result.fail(ResultCode.PARAM_ERROR.getCode(), message)); + } + /** * 处理运行时异常 */ diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java b/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java index c1ed0be..2b77b51 100644 --- a/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java +++ b/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java @@ -41,6 +41,7 @@ public class PaymentOrderServiceImpl implements PaymentOrderService { request.getMerchantTransactionId() ); if (existingOrder.isPresent()) { + log.warn("订单号已存在,商户订单号: {}", request.getMerchantTransactionId()); throw new BusinessException(ResultCode.ORDER_EXISTS); } @@ -93,29 +94,49 @@ public class PaymentOrderServiceImpl implements PaymentOrderService { @Override public Optional findByMerchantTransactionId(String merchantTransactionId) { - return paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId); + log.debug("查询支付订单,商户订单号: {}", merchantTransactionId); + Optional order = paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId); + if (order.isPresent()) { + log.debug("找到支付订单,商户订单号: {}, 订单状态: {}", merchantTransactionId, order.get().getStatus()); + } else { + log.debug("未找到支付订单,商户订单号: {}", merchantTransactionId); + } + return order; } @Override public Optional findByTransactionId(String transactionId) { - return paymentOrderMapper.findByTransactionId(transactionId); + log.debug("查询支付订单,PingPong交易流水号: {}", transactionId); + Optional order = paymentOrderMapper.findByTransactionId(transactionId); + if (order.isPresent()) { + log.debug("找到支付订单,PingPong交易流水号: {}, 订单状态: {}", transactionId, order.get().getStatus()); + } else { + log.debug("未找到支付订单,PingPong交易流水号: {}", transactionId); + } + return order; } @Override @Transactional public PaymentOrder updateOrderStatus(String merchantTransactionId, String status, String transactionId) { + log.info("更新订单状态,商户订单号: {}, 新状态: {}, PingPong交易流水号: {}", + merchantTransactionId, status, transactionId); Optional orderOpt = paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId); if (orderOpt.isEmpty()) { + log.warn("订单不存在,无法更新状态,商户订单号: {}", merchantTransactionId); throw new BusinessException(ResultCode.ORDER_NOT_FOUND); } PaymentOrder order = orderOpt.get(); + String oldStatus = order.getStatus(); order.setStatus(status); if (transactionId != null && !transactionId.isEmpty()) { order.setTransactionId(transactionId); } paymentOrderMapper.updateById(order); + log.info("订单状态更新成功,商户订单号: {}, 原状态: {}, 新状态: {}", + merchantTransactionId, oldStatus, status); return order; } } diff --git a/mt-pay/src/main/resources/application.yml b/mt-pay/src/main/resources/application.yml index 030b4f5..ceb7436 100644 --- a/mt-pay/src/main/resources/application.yml +++ b/mt-pay/src/main/resources/application.yml @@ -72,6 +72,16 @@ pingpong: # 服务器配置 server: port: 8080 + servlet: + context-path: / + # 文件上传配置 + multipart: + # 单个文件最大大小(10MB) + max-file-size: 10MB + # 请求最大大小(50MB,支持多文件上传) + max-request-size: 50MB + # 文件写入磁盘的阈值(超过此大小会写入临时文件) + file-size-threshold: 2MB # 阿里云OSS相关配置 aliyun: @@ -80,6 +90,4 @@ aliyun: accessKey: sAQR2swByBgmMOofH97hSJT638aVcJ endpoint: https://oss-cn-hangzhou.aliyuncs.com bucketName: mtkj2025 - servlet: - context-path: / diff --git a/mt-pay/src/main/resources/logback-spring.xml b/mt-pay/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..ee483f9 --- /dev/null +++ b/mt-pay/src/main/resources/logback-spring.xml @@ -0,0 +1,119 @@ + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + ${LOG_HOME}/mt-pay.%d{yyyy-MM-dd}.log + + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + 10MB + + + + + + + ${LOG_HOME}/mt-pay-error.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + 10MB + + + + ERROR + ACCEPT + DENY + + + + + + + 0 + + 512 + + + + + + 0 + 512 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +