chore(assets): 参赛提交规范红线修复(ASCII 化 + 相对路径)

按《参赛成果物提交规范·赛道一》§6 红线:
- samples/ 目录改名 sample/(git mv,保留历史)
- 10 个中日文样本文件 + docs 参赛手册 PDF 重命名为 ASCII
  (requirements_*/template_*/rules_*/contestant-handbook.pdf)
- tests/test_zh_template.py 硬编码绝对路径 D:\00_project\Genesis 改为相对路径
- 全局更新 21 个活动文件引用;历史日志/审查文档不改(追加说明记录)
全量 pytest 431 passed / 99.15%
This commit is contained in:
lhl
2026-08-26 14:15:52 +08:00
parent 1925ef7239
commit becd3e1f57
398 changed files with 286 additions and 72 deletions
@@ -0,0 +1,18 @@
package com.stock.trade.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 订单服务启动类
*
* @author tianxin
*/
@SpringBootApplication(scanBasePackages = {"com.stock.trade"})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
@@ -0,0 +1,84 @@
package com.stock.trade.order.controller;
import com.stock.trade.common.core.domain.CommonResult;
import com.stock.trade.common.core.domain.PageResult;
import com.stock.trade.order.controller.request.OrderCreateReqVO;
import com.stock.trade.order.controller.request.OrderPageReqVO;
import com.stock.trade.order.controller.request.OrderUpdateReqVO;
import com.stock.trade.order.controller.response.OrderRespVO;
import com.stock.trade.order.convert.OrderConvert;
import com.stock.trade.order.dal.dataobject.OrderDO;
import com.stock.trade.order.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static com.stock.trade.common.core.domain.CommonResult.success;
@Tag(name = "管理后台 - 订单")
@RestController
@RequestMapping("/trade/order")
@Validated
public class OrderController {
@Resource
private OrderService orderService;
@PostMapping("/create")
@Operation(summary = "创建订单")
@PreAuthorize("@ss.hasPermission('trade:order:create')")
public CommonResult<Long> createOrder(@Valid @RequestBody OrderCreateReqVO createReqVO) {
return success(orderService.createOrder(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新订单")
@PreAuthorize("@ss.hasPermission('trade:order:update')")
public CommonResult<Boolean> updateOrder(@Valid @RequestBody OrderUpdateReqVO updateReqVO) {
orderService.updateOrder(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除订单")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('trade:order:delete')")
public CommonResult<Boolean> deleteOrder(@RequestParam("id") Long id) {
orderService.deleteOrder(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:order:query')")
public CommonResult<OrderRespVO> getOrder(@RequestParam("id") Long id) {
OrderDO order = orderService.getOrder(id);
return success(OrderConvert.INSTANCE.convert(order));
}
@GetMapping("/page")
@Operation(summary = "获得订单分页")
@PreAuthorize("@ss.hasPermission('trade:order:query')")
public CommonResult<PageResult<OrderRespVO>> getOrderPage(@Valid OrderPageReqVO pageVO) {
PageResult<OrderDO> pageResult = orderService.getOrderPage(pageVO);
return success(OrderConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/cancel")
@Operation(summary = "撤销订单")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('trade:order:cancel')")
public CommonResult<Boolean> cancelOrder(@RequestParam("id") Long id,
@RequestParam("userId") Long userId) {
orderService.cancelOrder(id, userId);
return success(true);
}
}
@@ -0,0 +1,46 @@
package com.stock.trade.order.controller.request;
import com.stock.trade.order.enums.OrderDirectionEnum;
import com.stock.trade.order.enums.OrderTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 订单创建 Request VO")
@Data
public class OrderCreateReqVO {
@Schema(description = "用户编号", required = true, example = "1024")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "股票代码", required = true, example = "600000")
@NotEmpty(message = "股票代码不能为空")
private String stockCode;
@Schema(description = "股票名称", required = true, example = "浦发银行")
@NotEmpty(message = "股票名称不能为空")
private String stockName;
@Schema(description = "订单类型,参见 OrderTypeEnum 枚举", required = true, example = "0")
@NotNull(message = "订单类型不能为空")
private Integer type;
@Schema(description = "订单方向,参见 OrderDirectionEnum 枚举", required = true, example = "0")
@NotNull(message = "订单方向不能为空")
private Integer direction;
@Schema(description = "订单价格,市价单可为空", example = "10.24")
private BigDecimal price;
@Schema(description = "订单数量", required = true, example = "100")
@NotNull(message = "订单数量不能为空")
private Integer quantity;
@Schema(description = "备注", example = "测试订单")
private String remark;
}
@@ -0,0 +1,30 @@
package com.stock.trade.order.controller.request;
import com.stock.trade.common.core.dal.qo.PageQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 订单分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class OrderPageReqVO extends PageQuery {
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "股票代码", example = "600000")
private String stockCode;
@Schema(description = "订单类型,参见 OrderTypeEnum 枚举", example = "0")
private Integer type;
@Schema(description = "订单方向,参见 OrderDirectionEnum 枚举", example = "0")
private Integer direction;
@Schema(description = "订单状态,参见 OrderStatusEnum 枚举", example = "0")
private Integer status;
}
@@ -0,0 +1,26 @@
package com.stock.trade.order.controller.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 订单更新 Request VO")
@Data
public class OrderUpdateReqVO {
@Schema(description = "订单编号", required = true, example = "1024")
@NotNull(message = "订单编号不能为空")
private Long id;
@Schema(description = "订单价格,市价单可为空", example = "10.24")
private BigDecimal price;
@Schema(description = "订单数量", example = "100")
private Integer quantity;
@Schema(description = "备注", example = "修改订单")
private String remark;
}
@@ -0,0 +1,37 @@
package com.stock.trade.order.controller.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 订单项 Response VO")
@Data
public class OrderItemRespVO {
@Schema(description = "订单项编号", required = true, example = "1024")
private Long id;
@Schema(description = "订单编号", required = true, example = "2048")
private Long orderId;
@Schema(description = "成交编号", required = true, example = "T202305200001")
private String tradeNo;
@Schema(description = "成交价格", required = true, example = "10.24")
private BigDecimal price;
@Schema(description = "成交数量", required = true, example = "100")
private Integer quantity;
@Schema(description = "成交时间", required = true)
private LocalDateTime tradeTime;
@Schema(description = "交易费用", required = true, example = "5.12")
private BigDecimal fee;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}
@@ -0,0 +1,36 @@
package com.stock.trade.order.controller.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 订单日志 Response VO")
@Data
public class OrderLogRespVO {
@Schema(description = "日志编号", required = true, example = "1024")
private Long id;
@Schema(description = "订单编号", required = true, example = "2048")
private Long orderId;
@Schema(description = "操作前订单状态,参见 OrderStatusEnum 枚举", required = true, example = "0")
private Integer beforeStatus;
@Schema(description = "操作后订单状态,参见 OrderStatusEnum 枚举", required = true, example = "1")
private Integer afterStatus;
@Schema(description = "操作内容", required = true, example = "创建订单")
private String content;
@Schema(description = "操作时间", required = true)
private LocalDateTime operateTime;
@Schema(description = "操作人编号", required = true, example = "1001")
private Long operatorId;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}
@@ -0,0 +1,55 @@
package com.stock.trade.order.controller.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 订单 Response VO")
@Data
public class OrderRespVO {
@Schema(description = "订单编号", required = true, example = "1024")
private Long id;
@Schema(description = "用户编号", required = true, example = "1024")
private Long userId;
@Schema(description = "股票代码", required = true, example = "600000")
private String stockCode;
@Schema(description = "股票名称", required = true, example = "浦发银行")
private String stockName;
@Schema(description = "订单类型,参见 OrderTypeEnum 枚举", required = true, example = "0")
private Integer type;
@Schema(description = "订单方向,参见 OrderDirectionEnum 枚举", required = true, example = "0")
private Integer direction;
@Schema(description = "订单价格", example = "10.24")
private BigDecimal price;
@Schema(description = "订单数量", required = true, example = "100")
private Integer quantity;
@Schema(description = "已成交数量", required = true, example = "50")
private Integer filledQuantity;
@Schema(description = "订单状态,参见 OrderStatusEnum 枚举", required = true, example = "0")
private Integer status;
@Schema(description = "订单时间", required = true)
private LocalDateTime orderTime;
@Schema(description = "成交均价", example = "10.24")
private BigDecimal avgFillPrice;
@Schema(description = "备注", example = "测试订单")
private String remark;
@Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}
@@ -0,0 +1,21 @@
package com.stock.trade.order.convert;
import com.stock.trade.common.core.domain.PageResult;
import com.stock.trade.order.controller.request.OrderCreateReqVO;
import com.stock.trade.order.controller.response.OrderRespVO;
import com.stock.trade.order.dal.dataobject.OrderDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface OrderConvert {
OrderConvert INSTANCE = Mappers.getMapper(OrderConvert.class);
OrderDO convert(OrderCreateReqVO bean);
OrderRespVO convert(OrderDO bean);
PageResult<OrderRespVO> convertPage(PageResult<OrderDO> page);
}
@@ -0,0 +1,19 @@
package com.stock.trade.order.convert;
import com.stock.trade.order.controller.response.OrderItemRespVO;
import com.stock.trade.order.dal.dataobject.OrderItemDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface OrderItemConvert {
OrderItemConvert INSTANCE = Mappers.getMapper(OrderItemConvert.class);
OrderItemRespVO convert(OrderItemDO bean);
List<OrderItemRespVO> convertList(List<OrderItemDO> list);
}
@@ -0,0 +1,19 @@
package com.stock.trade.order.convert;
import com.stock.trade.order.controller.response.OrderLogRespVO;
import com.stock.trade.order.dal.dataobject.OrderLogDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface OrderLogConvert {
OrderLogConvert INSTANCE = Mappers.getMapper(OrderLogConvert.class);
OrderLogRespVO convert(OrderLogDO bean);
List<OrderLogRespVO> convertList(List<OrderLogDO> list);
}
@@ -0,0 +1,89 @@
package com.stock.trade.order.dal.dataobject;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.stock.trade.common.core.dataobject.BaseDO;
import com.stock.trade.order.enums.OrderDirectionEnum;
import com.stock.trade.order.enums.OrderStatusEnum;
import com.stock.trade.order.enums.OrderTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单 DO
*
* @author xxx
*/
@TableName("trade_order")
@KeySequence("trade_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class OrderDO extends BaseDO {
/**
* 订单编号,主键自增
*/
@TableId
private Long id;
/**
* 用户编号
*/
private Long userId;
/**
* 股票代码
*/
private String stockCode;
/**
* 股票名称
*/
private String stockName;
/**
* 订单类型
*
* 枚举 {@link OrderTypeEnum}
*/
private Integer type;
/**
* 订单方向
*
* 枚举 {@link OrderDirectionEnum}
*/
private Integer direction;
/**
* 订单价格
*/
private BigDecimal price;
/**
* 订单数量
*/
private Integer quantity;
/**
* 已成交数量
*/
private Integer filledQuantity;
/**
* 订单状态
*
* 枚举 {@link OrderStatusEnum}
*/
private Integer status;
/**
* 订单时间
*/
private LocalDateTime orderTime;
/**
* 成交均价
*/
private BigDecimal avgFillPrice;
/**
* 备注
*/
private String remark;
}
@@ -0,0 +1,56 @@
package com.stock.trade.order.dal.dataobject;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.stock.trade.common.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单项 DO
*
* @author xxx
*/
@TableName("trade_order_item")
@KeySequence("trade_order_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class OrderItemDO extends BaseDO {
/**
* 订单项编号,主键自增
*/
@TableId
private Long id;
/**
* 订单编号,关联 {@link OrderDO#getId()}
*/
private Long orderId;
/**
* 成交编号 (如果部分成交,一个订单可能会有多条成交记录)
*/
private String tradeNo;
/**
* 成交价格
*/
private BigDecimal fillPrice;
/**
* 成交数量
*/
private Integer fillQuantity;
/**
* 成交时间
*/
private LocalDateTime fillTime;
/**
* 交易费用
*/
private BigDecimal commission;
}
@@ -0,0 +1,60 @@
package com.stock.trade.order.dal.dataobject;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.stock.trade.common.core.dataobject.BaseDO;
import com.stock.trade.order.enums.OrderStatusEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
/**
* 订单日志 DO
*
* @author xxx
*/
@TableName("trade_order_log")
@KeySequence("trade_order_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class OrderLogDO extends BaseDO {
/**
* 日志编号,主键自增
*/
@TableId
private Long id;
/**
* 订单编号,关联 {@link OrderDO#getId()}
*/
private Long orderId;
/**
* 操作前订单状态
*
* 枚举 {@link OrderStatusEnum}
*/
private Integer beforeStatus;
/**
* 操作后订单状态
*
* 枚举 {@link OrderStatusEnum}
*/
private Integer afterStatus;
/**
* 操作内容
*/
private String content;
/**
* 操作时间
*/
private LocalDateTime operationTime;
/**
* 操作人编号 (系统操作时,可以为空)
*/
private Long operatorUserId;
}
@@ -0,0 +1,25 @@
package com.stock.trade.order.dal.mysql;
import com.stock.trade.common.core.dal.mapper.BaseMapperX;
import com.stock.trade.order.dal.dataobject.OrderItemDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 订单项 Mapper
*
* @author xxx
*/
@Mapper
public interface OrderItemMapper extends BaseMapperX<OrderItemDO> {
default List<OrderItemDO> selectListByOrderId(Long orderId) {
return selectList(OrderItemDO::getOrderId, orderId);
}
default List<OrderItemDO> selectListByOrderIds(List<Long> orderIds) {
return selectList(new LambdaQueryWrapperX<OrderItemDO>()
.in(OrderItemDO::getOrderId, orderIds));
}
}
@@ -0,0 +1,22 @@
package com.stock.trade.order.dal.mysql;
import com.stock.trade.common.core.dal.mapper.BaseMapperX;
import com.stock.trade.order.dal.dataobject.OrderLogDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 订单日志 Mapper
*
* @author xxx
*/
@Mapper
public interface OrderLogMapper extends BaseMapperX<OrderLogDO> {
default List<OrderLogDO> selectListByOrderId(Long orderId) {
return selectList(new LambdaQueryWrapperX<OrderLogDO>()
.eq(OrderLogDO::getOrderId, orderId)
.orderByDesc(OrderLogDO::getId)); // 按时间倒序,最新的日志在前面
}
}
@@ -0,0 +1,41 @@
package com.stock.trade.order.dal.mysql;
import com.stock.trade.common.core.dal.mapper.BaseMapperX;
import com.stock.trade.common.core.dal.qo.PageQuery;
import com.stock.trade.order.dal.dataobject.OrderDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 订单 Mapper
*
* @author xxx
*/
@Mapper
public interface OrderMapper extends BaseMapperX<OrderDO> {
default List<OrderDO> selectPage(PageQuery pageQuery, Long userId, String stockCode, Integer type, Integer direction, Integer status) {
return selectList(new LambdaQueryWrapperX<OrderDO>()
.eqIfPresent(OrderDO::getUserId, userId)
.likeIfPresent(OrderDO::getStockCode, stockCode)
.eqIfPresent(OrderDO::getType, type)
.eqIfPresent(OrderDO::getDirection, direction)
.eqIfPresent(OrderDO::getStatus, status)
.orderByDesc(OrderDO::getId),
pageQuery.getPageNo(), pageQuery.getPageSize());
}
default Long selectCount(Long userId, String stockCode, Integer type, Integer direction, Integer status) {
return selectCount(new LambdaQueryWrapperX<OrderDO>()
.eqIfPresent(OrderDO::getUserId, userId)
.likeIfPresent(OrderDO::getStockCode, stockCode)
.eqIfPresent(OrderDO::getType, type)
.eqIfPresent(OrderDO::getDirection, direction)
.eqIfPresent(OrderDO::getStatus, status));
}
default List<OrderDO> selectListByUserId(Long userId) {
return selectList(OrderDO::getUserId, userId);
}
}
@@ -0,0 +1,29 @@
package com.stock.trade.order.enums;
import com.stock.trade.common.exception.ErrorCode;
/**
* trade-order 模块错误码枚举
*
* @author xxx
*/
public interface ErrorCodeConstants {
// ========== 订单 1003001000 开头 ============
ErrorCode ORDER_NOT_EXISTS = new ErrorCode(1003001000, "订单不存在");
ErrorCode ORDER_CREATE_FAILED = new ErrorCode(1003001001, "订单创建失败");
ErrorCode ORDER_UPDATE_FAILED = new ErrorCode(1003001002, "订单更新失败");
ErrorCode ORDER_DELETE_FAILED = new ErrorCode(1003001003, "订单删除失败");
ErrorCode ORDER_STATUS_INVALID = new ErrorCode(1003001004, "订单状态不合法");
ErrorCode ORDER_PRICE_INVALID = new ErrorCode(1003001005, "订单价格不合法");
ErrorCode ORDER_QUANTITY_INVALID = new ErrorCode(1003001006, "订单数量不合法");
ErrorCode ORDER_TYPE_INVALID = new ErrorCode(1003001007, "订单类型不合法");
ErrorCode ORDER_DIRECTION_INVALID = new ErrorCode(1003001008, "订单方向不合法");
// ========== 订单项 1003002000 开头 ============
ErrorCode ORDER_ITEM_NOT_EXISTS = new ErrorCode(1003002000, "订单项不存在");
// ========== 订单日志 1003003000 开头 ============
ErrorCode ORDER_LOG_NOT_EXISTS = new ErrorCode(1003003000, "订单日志不存在");
}
@@ -0,0 +1,36 @@
package com.stock.trade.order.enums;
import com.stock.trade.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 订单方向枚举
*
* @author xxx
*/
@Getter
@AllArgsConstructor
public enum OrderDirectionEnum implements IntArrayValuable {
BUY(0, "买入"),
SELL(1, "卖出");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(OrderDirectionEnum::getDirection).toArray();
/**
* 方向编码
*/
private final Integer direction;
/**
* 方向描述
*/
private final String desc;
@Override
public int[] array() {
return ARRAYS;
}
}
@@ -0,0 +1,66 @@
package com.stock.trade.order.enums;
import com.stock.trade.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 订单状态枚举
*
* @author xxx
*/
@Getter
@AllArgsConstructor
public enum OrderStatusEnum implements IntArrayValuable {
PENDING_NEW(0, "待报"), // 订单已创建,但尚未发送到交易所
NEW(1, "已报"), // 订单已发送到交易所,等待撮合
PARTIALLY_FILLED(2, "部分成交"),
FILLED(3, "全部成交"),
CANCELED(4, "已撤销"),
REJECTED(5, "已拒绝"), // 订单被交易所拒绝
EXPIRED(6, "已过期"); // 订单因过期未成交而失效
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(OrderStatusEnum::getStatus).toArray();
/**
* 状态编码
*/
private final Integer status;
/**
* 状态描述
*/
private final String desc;
@Override
public int[] array() {
return ARRAYS;
}
/**
* 判断订单是否为最终状态 (不可再变更)
*
* @param status 订单状态
* @return 是否为最终状态
*/
public static boolean isFinalStatus(Integer status) {
return FILLED.getStatus().equals(status)
|| CANCELED.getStatus().equals(status)
|| REJECTED.getStatus().equals(status)
|| EXPIRED.getStatus().equals(status);
}
/**
* 判断订单是否可以被撤销
*
* @param status 订单状态
* @return 是否可以被撤销
*/
public static boolean canCancel(Integer status) {
return PENDING_NEW.getStatus().equals(status)
|| NEW.getStatus().equals(status)
|| PARTIALLY_FILLED.getStatus().equals(status);
}
}
@@ -0,0 +1,39 @@
package com.stock.trade.order.enums;
import com.stock.trade.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 订单类型枚举
*
* @author xxx
*/
@Getter
@AllArgsConstructor
public enum OrderTypeEnum implements IntArrayValuable {
LIMIT(0, "限价单"),
MARKET(1, "市价单"),
STOP(2, "止损单"),
STOP_LIMIT(3, "止损限价单");
// TODO 后续可以根据实际需求扩展更多订单类型,例如 FOK, FAK 等
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(OrderTypeEnum::getType).toArray();
/**
* 类型编码
*/
private final Integer type;
/**
* 类型描述
*/
private final String desc;
@Override
public int[] array() {
return ARRAYS;
}
}
@@ -0,0 +1,32 @@
package com.stock.trade.order.mq.consumer;
import com.stock.trade.order.service.OrderService;
import com.stock.trade.tradeengine.message.TradeOrderReturnMessage; // 假设成交回报消息定义在 trade-engine 模块
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 订单成交回报消息消费者
*/
@Component
@Slf4j
public class TradeOrderReturnConsumer {
@Resource
private OrderService orderService;
@EventListener
public void onMessage(TradeOrderReturnMessage message) {
log.info("[onMessage][消息内容({})]", message);
try {
orderService.processOrderReturn(message);
} catch (Throwable e) {
log.error("[onMessage][处理订单成交回报({}) 异常]", message, e);
// TODO: 考虑增加重试机制或死信队列
}
}
}
@@ -0,0 +1,37 @@
package com.stock.trade.order.mq.message;
import com.stock.trade.common.mq.message.AbstractStreamMessage;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = true)
public class OrderMessage extends AbstractStreamMessage {
public static final String STREAM_KEY = "stock.trade.order.change";
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 订单状态
*/
@NotNull(message = "订单状态不能为空")
private Integer status;
@Override
public String getStreamKey() {
return STREAM_KEY;
}
}
@@ -0,0 +1,9 @@
package com.stock.trade.order.mq.producer;
import com.stock.trade.common.mq.producer.AbstractStreamProducer;
import com.stock.trade.order.mq.message.OrderMessage;
import org.springframework.stereotype.Component;
@Component
public class OrderProducer extends AbstractStreamProducer<OrderMessage> {
}
@@ -0,0 +1,38 @@
package com.stock.trade.order.service;
import com.stock.trade.order.dal.dataobject.OrderItemDO;
import java.util.List;
/**
* 订单项 Service 接口
*
* @author xxx
*/
public interface OrderItemService {
/**
* 根据订单编号获取订单项列表
*
* @param orderId 订单编号
* @return 订单项列表
*/
List<OrderItemDO> getOrderItemListByOrderId(Long orderId);
/**
* 根据订单编号列表获取订单项列表
*
* @param orderIds 订单编号列表
* @return 订单项列表
*/
List<OrderItemDO> getOrderItemListByOrderIds(List<Long> orderIds);
/**
* 创建订单项
*
* @param orderItem 订单项信息
* @return 订单项编号
*/
Long createOrderItem(OrderItemDO orderItem);
}
@@ -0,0 +1,29 @@
package com.stock.trade.order.service;
import com.stock.trade.order.dal.dataobject.OrderLogDO;
import java.util.List;
/**
* 订单日志 Service 接口
*
* @author xxx
*/
public interface OrderLogService {
/**
* 创建订单日志
*
* @param orderLog 订单日志信息
*/
void createOrderLog(OrderLogDO orderLog);
/**
* 根据订单编号获取订单日志列表
*
* @param orderId 订单编号
* @return 订单日志列表
*/
List<OrderLogDO> getOrderLogListByOrderId(Long orderId);
}
@@ -0,0 +1,101 @@
package com.stock.trade.order.service;
import com.stock.trade.common.core.domain.PageResult;
import com.stock.trade.order.controller.request.OrderCreateReqVO;
import com.stock.trade.order.controller.request.OrderPageReqVO;
import com.stock.trade.order.controller.request.OrderUpdateReqVO;
import com.stock.trade.order.dal.dataobject.OrderDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* 订单 Service 接口
*
* @author xxx
*/
public interface OrderService {
/**
* 创建订单
*
* @param createReqVO 创建信息
* @return 订单编号
*/
Long createOrder(@Valid OrderCreateReqVO createReqVO);
/**
* 更新订单
*
* @param updateReqVO 更新信息
*/
void updateOrder(@Valid OrderUpdateReqVO updateReqVO);
/**
* 更新订单状态
*
* @param id 订单编号
* @param status 订单状态
* @param remark 备注 (可选)
*/
void updateOrderStatus(Long id, Integer status, String remark);
/**
* 撤销订单
*
* @param id 订单编号
* @param userId 用户编号 (用于权限校验)
*/
void cancelOrder(Long id, Long userId);
/**
* 删除订单 (逻辑删除)
*
* @param id 订单编号
*/
void deleteOrder(Long id);
/**
* 获取订单信息
*
* @param id 订单编号
* @return 订单信息
*/
OrderDO getOrder(Long id);
/**
* 获取订单列表
*
* @param ids 订单编号集合
* @return 订单列表
*/
List<OrderDO> getOrderList(Collection<Long> ids);
/**
* 获取订单分页
*
* @param pageReqVO 分页查询参数
* @return 订单分页结果
*/
PageResult<OrderDO> getOrderPage(OrderPageReqVO pageReqVO);
/**
* 根据用户编号获取其所有未完成订单列表
*
* @param userId 用户编号
* @return 未完成订单列表
*/
List<OrderDO> getUnfinishedOrdersByUserId(Long userId);
/**
* 处理订单成交回报
*
* @param orderId 订单编号
* @param fillPrice 成交价格
* @param fillQuantity 成交数量
* @param tradeNo 成交编号
*/
void processOrderFill(Long orderId, String stockCode, Integer direction, java.math.BigDecimal fillPrice, Integer fillQuantity, String tradeNo);
}
@@ -0,0 +1,45 @@
package com.stock.trade.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.stock.trade.order.dal.dataobject.OrderItemDO;
import com.stock.trade.order.dal.mysql.OrderItemMapper;
import com.stock.trade.order.service.OrderItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* 订单项 Service 实现类
*
* @author xxx
*/
@Service
@Slf4j
public class OrderItemServiceImpl implements OrderItemService {
@Resource
private OrderItemMapper orderItemMapper;
@Override
public List<OrderItemDO> getOrderItemListByOrderId(Long orderId) {
return orderItemMapper.selectListByOrderId(orderId);
}
@Override
public List<OrderItemDO> getOrderItemListByOrderIds(List<Long> orderIds) {
if (CollUtil.isEmpty(orderIds)) {
return Collections.emptyList();
}
return orderItemMapper.selectListByOrderIds(orderIds);
}
@Override
public Long createOrderItem(OrderItemDO orderItem) {
orderItemMapper.insert(orderItem);
return orderItem.getId();
}
}
@@ -0,0 +1,34 @@
package com.stock.trade.order.service.impl;
import com.stock.trade.order.dal.dataobject.OrderLogDO;
import com.stock.trade.order.dal.mysql.OrderLogMapper;
import com.stock.trade.order.service.OrderLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 订单日志 Service 实现类
*
* @author xxx
*/
@Service
@Slf4j
public class OrderLogServiceImpl implements OrderLogService {
@Resource
private OrderLogMapper orderLogMapper;
@Override
public void createOrderLog(OrderLogDO orderLog) {
orderLogMapper.insert(orderLog);
}
@Override
public List<OrderLogDO> getOrderLogListByOrderId(Long orderId) {
return orderLogMapper.selectListByOrderId(orderId);
}
}
@@ -0,0 +1,345 @@
package com.stock.trade.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.stock.trade.common.core.domain.PageResult;
import com.stock.trade.common.core.exception.ServiceException;
import com.stock.trade.order.controller.request.OrderCreateReqVO;
import com.stock.trade.order.controller.request.OrderPageReqVO;
import com.stock.trade.order.controller.request.OrderUpdateReqVO;
import com.stock.trade.order.convert.OrderConvert;
import com.stock.trade.order.dal.dataobject.OrderDO;
import com.stock.trade.order.dal.dataobject.OrderItemDO;
import com.stock.trade.order.dal.dataobject.OrderLogDO;
import com.stock.trade.order.dal.mysql.OrderMapper;
import com.stock.trade.order.enums.ErrorCodeConstants;
import com.stock.trade.order.enums.OrderStatusEnum;
import com.stock.trade.order.service.OrderItemService;
import com.stock.trade.order.service.OrderLogService;
import com.stock.trade.order.service.OrderService;
import com.stock.trade.order.mq.message.OrderMessage;
import com.stock.trade.order.mq.producer.OrderProducer;
import com.stock.trade.order.mq.message.TradeOrderReturnMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import static com.stock.trade.common.core.exception.util.ServiceExceptionUtil.exception;
/**
* 订单 Service 实现类
*
* @author xxx
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private OrderItemService orderItemService;
@Resource
private OrderLogService orderLogService;
@Resource
private OrderProducer orderProducer;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderCreateReqVO createReqVO) {
// 1. 校验订单参数
validateCreateOrder(createReqVO);
// 2. 创建订单
OrderDO order = OrderConvert.INSTANCE.convert(createReqVO);
order.setStatus(OrderStatusEnum.PENDING_NEW.getStatus());
order.setOrderTime(LocalDateTime.now());
order.setFilledQuantity(0);
orderMapper.insert(order);
// 3. 创建订单日志
OrderLogDO orderLog = new OrderLogDO()
.setOrderId(order.getId())
.setBeforeStatus(null)
.setAfterStatus(order.getStatus())
.setContent("创建订单")
.setOperationTime(LocalDateTime.now())
.setOperatorUserId(order.getUserId());
orderLogService.createOrderLog(orderLog);
// 发送订单状态变更消息
orderProducer.sendOrderMessage(new OrderMessage().setOrderId(order.getId()).setUserId(order.getUserId()).setStatus(order.getStatus()));
return order.getId();
}
private void validateCreateOrder(OrderCreateReqVO createReqVO) {
// 1. 校验价格
if (createReqVO.getPrice() != null && createReqVO.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
throw exception(ErrorCodeConstants.ORDER_PRICE_INVALID);
}
// 2. 校验数量
if (createReqVO.getQuantity() <= 0) {
throw exception(ErrorCodeConstants.ORDER_QUANTITY_INVALID);
}
// 3. 校验订单类型
if (!OrderTypeEnum.ARRAYS.contains(createReqVO.getType())) {
throw exception(ErrorCodeConstants.ORDER_TYPE_INVALID);
}
// 4. 校验订单方向
if (!OrderDirectionEnum.ARRAYS.contains(createReqVO.getDirection())) {
throw exception(ErrorCodeConstants.ORDER_DIRECTION_INVALID);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrder(OrderUpdateReqVO updateReqVO) {
// 1. 校验订单存在
OrderDO order = validateOrderExists(updateReqVO.getId());
// 2. 校验订单状态是否允许修改
if (OrderStatusEnum.isFinalStatus(order.getStatus())) {
throw exception(ErrorCodeConstants.ORDER_STATUS_INVALID);
}
// 3. 更新订单
OrderDO updateObj = OrderConvert.INSTANCE.convert(updateReqVO);
orderMapper.updateById(updateObj);
// 4. 创建订单日志
OrderLogDO orderLog = new OrderLogDO()
.setOrderId(order.getId())
.setBeforeStatus(order.getStatus())
.setAfterStatus(order.getStatus())
.setContent("更新订单信息")
.setOperationTime(LocalDateTime.now())
.setOperatorUserId(order.getUserId());
orderLogService.createOrderLog(orderLog);
// 发送订单状态变更消息
orderProducer.sendOrderMessage(new OrderMessage().setOrderId(order.getId()).setUserId(userId).setStatus(OrderStatusEnum.CANCELED.getStatus()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long id, Integer status, String remark) {
// 1. 校验订单存在
OrderDO order = validateOrderExists(id);
// 2. 更新订单状态
OrderDO updateObj = new OrderDO()
.setId(id)
.setStatus(status)
.setRemark(remark);
orderMapper.updateById(updateObj);
// 3. 创建订单日志
OrderLogDO orderLog = new OrderLogDO()
.setOrderId(order.getId())
.setBeforeStatus(order.getStatus())
.setAfterStatus(status)
.setContent("更新订单状态")
.setOperationTime(LocalDateTime.now())
.setOperatorUserId(order.getUserId());
orderLogService.createOrderLog(orderLog);
// 发送订单状态变更消息
orderProducer.sendOrderMessage(new OrderMessage().setOrderId(order.getId()).setUserId(userId).setStatus(OrderStatusEnum.CANCELED.getStatus()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(Long id, Long userId) {
// 1. 校验订单存在
OrderDO order = validateOrderExists(id);
// 2. 校验订单是否属于当前用户
if (!order.getUserId().equals(userId)) {
throw exception(ErrorCodeConstants.ORDER_NOT_EXISTS);
}
// 3. 校验订单状态是否允许撤销
if (!OrderStatusEnum.canCancel(order.getStatus())) {
throw exception(ErrorCodeConstants.ORDER_STATUS_INVALID);
}
// 4. 更新订单状态为已撤销
OrderDO updateObj = new OrderDO()
.setId(id)
.setStatus(OrderStatusEnum.CANCELED.getStatus())
.setRemark("用户主动撤销");
orderMapper.updateById(updateObj);
// 5. 创建订单日志
OrderLogDO orderLog = new OrderLogDO()
.setOrderId(order.getId())
.setBeforeStatus(order.getStatus())
.setAfterStatus(OrderStatusEnum.CANCELED.getStatus())
.setContent("撤销订单")
.setOperationTime(LocalDateTime.now())
.setOperatorUserId(userId);
orderLogService.createOrderLog(orderLog);
// 发送订单状态变更消息
orderProducer.sendOrderMessage(new OrderMessage().setOrderId(order.getId()).setUserId(userId).setStatus(OrderStatusEnum.CANCELED.getStatus()));
}
@Override
public void deleteOrder(Long id) {
// 校验订单存在
validateOrderExists(id);
// 删除订单
orderMapper.deleteById(id);
}
private OrderDO validateOrderExists(Long id) {
OrderDO order = orderMapper.selectById(id);
if (order == null) {
throw exception(ErrorCodeConstants.ORDER_NOT_EXISTS);
}
return order;
}
@Override
public OrderDO getOrder(Long id) {
return orderMapper.selectById(id);
}
@Override
public List<OrderDO> getOrderList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return CollUtil.newArrayList();
}
return orderMapper.selectBatchIds(ids);
}
@Override
public PageResult<OrderDO> getOrderPage(OrderPageReqVO pageReqVO) {
// 查询订单分页
List<OrderDO> list = orderMapper.selectPage(pageReqVO.getPageQuery(),
pageReqVO.getUserId(), pageReqVO.getStockCode(), pageReqVO.getType(),
pageReqVO.getDirection(), pageReqVO.getStatus());
// 查询总数
Long total = orderMapper.selectCount(pageReqVO.getUserId(), pageReqVO.getStockCode(),
pageReqVO.getType(), pageReqVO.getDirection(), pageReqVO.getStatus());
return new PageResult<>(list, total);
}
@Override
public List<OrderDO> getUnfinishedOrdersByUserId(Long userId) {
return orderMapper.selectList(new LambdaQueryWrapperX<OrderDO>()
.eq(OrderDO::getUserId, userId)
.in(OrderDO::getStatus, OrderStatusEnum.PENDING_NEW.getStatus(),
OrderStatusEnum.NEW.getStatus(),
OrderStatusEnum.PARTIALLY_FILLED.getStatus()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void processOrderReturn(TradeOrderReturnMessage message) {
log.info("[processOrderReturn][接收到订单成交回报消息:{}]", message);
processOrderFillInternal(message.getOrderId(), message.getStockCode(), message.getDirection(),
message.getFillPrice(), message.getFillQuantity(), message.getTradeNo(), true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void processOrderFill(Long orderId, String stockCode, Integer direction,
BigDecimal fillPrice, Integer fillQuantity, String tradeNo) {
processOrderFillInternal(orderId, stockCode, direction, fillPrice, fillQuantity, tradeNo, false);
}
private void processOrderFillInternal(Long orderId, String stockCode, Integer direction,
BigDecimal fillPrice, Integer fillQuantity, String tradeNo, boolean fromMq) {
// 1. 校验订单存在
OrderDO order = validateOrderExists(orderId);
// 2. 校验成交回报参数
if (!order.getStockCode().equals(stockCode) || !order.getDirection().equals(direction)) {
log.error("[processOrderFillInternal][订单({})的股票代码({})或方向({})不匹配, fromMq:{}]", orderId, stockCode, direction, fromMq);
throw exception(ErrorCodeConstants.ORDER_NOT_EXISTS);
}
// 3. 创建订单项
OrderItemDO orderItem = new OrderItemDO()
.setOrderId(orderId)
.setTradeNo(tradeNo)
.setFillPrice(fillPrice)
.setFillQuantity(fillQuantity)
.setFillTime(LocalDateTime.now())
.setCommission(calculateCommission(fillPrice, fillQuantity)); // 计算手续费
orderItemService.createOrderItem(orderItem);
// 4. 更新订单成交信息
Integer newFilledQuantity = order.getFilledQuantity() + fillQuantity;
BigDecimal newAvgFillPrice = calculateAvgFillPrice(order.getAvgFillPrice(),
order.getFilledQuantity(), fillPrice, fillQuantity);
Integer newStatus = newFilledQuantity.equals(order.getQuantity()) ?
OrderStatusEnum.FILLED.getStatus() : OrderStatusEnum.PARTIALLY_FILLED.getStatus();
OrderDO updateObj = new OrderDO()
.setId(orderId)
.setFilledQuantity(newFilledQuantity)
.setAvgFillPrice(newAvgFillPrice)
.setStatus(newStatus);
orderMapper.updateById(updateObj);
// 5. 创建订单日志
OrderLogDO orderLog = new OrderLogDO()
.setOrderId(orderId)
.setBeforeStatus(order.getStatus())
.setAfterStatus(newStatus)
.setContent(String.format("订单成交: 成交数量 %d, 成交价格 %.3f", fillQuantity, fillPrice))
.setOperationTime(LocalDateTime.now())
.setOperatorUserId(order.getUserId()); // 补充操作用户ID
orderLogService.createOrderLog(orderLog);
// 6. 发送订单状态变更消息
orderProducer.sendOrderMessage(new OrderMessage().setOrderId(orderId).setUserId(order.getUserId()).setStatus(newStatus));
log.info("[processOrderFillInternal][订单({})处理完成,来源MQ{}]", orderId, fromMq);
}
/**
* 计算手续费
*
* @param price 成交价格
* @param quantity 成交数量
* @return 手续费
*/
private BigDecimal calculateCommission(BigDecimal price, Integer quantity) {
// TODO 根据实际业务规则计算手续费
BigDecimal amount = price.multiply(new BigDecimal(quantity));
return amount.multiply(new BigDecimal("0.0003")).setScale(2, RoundingMode.UP); // 暂定千分之三
}
/**
* 计算成交均价
*
* @param oldAvgPrice 原成交均价
* @param oldQuantity 原成交数量
* @param newPrice 新成交价格
* @param newQuantity 新成交数量
* @return 新的成交均价
*/
private BigDecimal calculateAvgFillPrice(BigDecimal oldAvgPrice, Integer oldQuantity,
BigDecimal newPrice, Integer newQuantity) {
if (oldAvgPrice == null || oldQuantity == 0) {
return newPrice;
}
BigDecimal totalAmount = oldAvgPrice.multiply(new BigDecimal(oldQuantity))
.add(newPrice.multiply(new BigDecimal(newQuantity)));
return totalAmount.divide(new BigDecimal(oldQuantity + newQuantity), 3, RoundingMode.HALF_UP);
}
}
@@ -0,0 +1,39 @@
package com.trade.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
/**
* 订单服务启动类
* @author Trade Team
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.trade.user.api"}) // 扫描Feign客户端,例如用户服务的API
@MapperScan("com.trade.order.mapper") // 扫描MyBatis Mapper接口
@ComponentScan(basePackages = {"com.trade.order", "com.trade.common.config"}) // 扫描通用配置和当前模块
public class OrderApplication {
/**
* 主函数,启动订单服务应用。
*
* @param args 命令行参数
*/
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ Trade Order Service 启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ | \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
@@ -0,0 +1,17 @@
package com.trade.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 订单服务启动类
* Created by macro on 2020/8/3.
*/
@EnableDiscoveryClient
@SpringBootApplication
public class TradeOrderApplication {
public static void main(String[] args) {
SpringApplication.run(TradeOrderApplication.class, args);
}
}
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的配置类。
*/
package com.trade.order.config;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块相关的常量定义。
*/
package com.trade.order.constant;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的Controller层,负责处理HTTP请求和响应。
*/
package com.trade.order.controller;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的数据传输对象 (DTO)。
*/
package com.trade.order.dto;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的数据库实体类。
*/
package com.trade.order.entity;
@@ -0,0 +1,5 @@
/**
* 此包存放订单模块相关的枚举类。
* 例如:订单状态枚举、支付方式枚举等。
*/
package com.trade.order.enums;
@@ -0,0 +1,4 @@
/**
* 此包存放订单服务模块的事件监听器,例如监听支付成功事件、库存扣减事件等。
*/
package com.trade.order.listener;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的Mapper接口,用于数据库操作。
*/
package com.trade.order.mapper;
@@ -0,0 +1,4 @@
/**
* 订单服务模块主包。
*/
package com.trade.order;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块Service接口的实现类。
*/
package com.trade.order.service.impl;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的Service层,负责业务逻辑处理。
*/
package com.trade.order.service;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块相关的工具类。
*/
package com.trade.order.util;
@@ -0,0 +1,4 @@
/**
* 此包存放订单服务模块的工具类。
*/
package com.trade.order.utils;
@@ -0,0 +1,4 @@
/**
* 此包存放订单模块的视图对象 (VO)。
*/
package com.trade.order.vo;
@@ -0,0 +1,11 @@
server:
port: 8003
spring:
profiles:
active: dev
application:
name: trade-order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
@@ -0,0 +1,11 @@
server:
port: 8003
spring:
profiles:
active: prod
application:
name: trade-order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
@@ -0,0 +1,11 @@
server:
port: 8003
spring:
profiles:
active: test
application:
name: trade-order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
@@ -0,0 +1,111 @@
server:
port: 8082 # 订单服务端口,避免与网关或其他服务冲突
spring:
application:
name: trade-order # 应用名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos Server地址
namespace: # Nacos命名空间ID,根据实际情况配置
group: DEFAULT_GROUP # Nacos分组,根据实际情况配置
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr} # Nacos配置中心地址
namespace: ${spring.cloud.nacos.discovery.namespace}
group: ${spring.cloud.nacos.discovery.group}
file-extension: yml # 配置文件格式
shared-configs: # 共享配置
- data-id: application-common.yml # 通用应用配置
group: ${spring.cloud.nacos.discovery.group}
refresh: true
- data-id: datasource-mysql-config.yml # MySQL数据源配置
group: ${spring.cloud.nacos.discovery.group}
refresh: true
# ext-config: # 可选的扩展配置
# - data-id: trade-order-ext.yml
# group: ${spring.cloud.nacos.discovery.group}
# refresh: true
# Spring Profiles: 用于区分不同环境的配置 (dev, test, prod)
profiles:
active: dev # 默认激活开发环境配置
# MyBatis Plus 配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml # Mapper XML文件位置
# type-aliases-package: com.trade.order.entity # 实体类别名扫描包 (如果需要)
global-config:
db-config:
id-type: auto # 全局主键策略
# table-prefix: t_ # 全局表前缀
# logic-delete-field: deleted # 全局逻辑删除字段名
# logic-not-delete-value: 0
# logic-delete-value: 1
configuration:
map-underscore-to-camel-case: true # 开启驼峰命名转换
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # MyBatis日志实现
# 日志配置
logging:
level:
com.trade.order: INFO # 订单模块日志级别
com.trade.order.mapper: DEBUG # Mapper接口日志级别 (开发时可设为DEBUG查看SQL)
org.springframework.web: INFO
org.springframework.security: INFO # 如果集成了Spring Security
# file:
# name: ./logs/trade-order.log # 日志文件路径和名称
# Actuator端点配置
management:
endpoints:
web:
exposure:
include: '*' # 暴露所有端点,生产环境请按需配置
endpoint:
health:
show-details: always # 显示健康检查详情
# Feign 配置 (如果需要调用其他服务)
feign:
client:
config:
default:
connectTimeout: 5000 # 连接超时时间 (ms)
readTimeout: 5000 # 读取超时时间 (ms)
# sentinel:
# enabled: true # 开启Sentinel对Feign的支持 (如果集成了Sentinel)
# Seata 分布式事务配置 (如果需要)
# seata:
# tx-service-group: trade_tx_group # 事务分组,需要与Seata Server配置一致
# service:
# vgroup-mapping:
# trade_tx_group: default
# registry:
# type: nacos
# nacos:
# server-addr: ${spring.cloud.nacos.discovery.server-addr}
# namespace: ${spring.cloud.nacos.discovery.namespace}
# group: DEFAULT_GROUP
# application: seata-server
# config:
# type: nacos
# nacos:
# server-addr: ${spring.cloud.nacos.discovery.server-addr}
# namespace: ${spring.cloud.nacos.discovery.namespace}
# group: SEATA_GROUP
# data-id: seataServer.properties
# Swagger/OpenAPI 配置 (如果使用SpringDoc)
# springdoc:
# api-docs:
# path: /v3/api-docs
# swagger-ui:
# path: /swagger-ui.html
# display-request-duration: true
# groups-order: DESC
# group-configs:
# - group: 'Order API'
# paths-to-match: '/order/**'
# packages-to-scan: com.trade.order.controller
@@ -0,0 +1,102 @@
spring:
application:
name: trade-order # 服务名
cloud:
nacos:
# Nacos 作为注册中心的配置项
discovery:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
# namespace: # Nacos 命名空间
# group: # Nacos 分组
# Nacos 作为配置中心的配置项
config:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
file-extension: yaml # 文件后缀名
# namespace: # Nacos 命名空间
# group: # Nacos 分组
# shared-configs: # 共享配置
# - data-id: common.yaml # 共享配置的 Data ID
# group: DEFAULT_GROUP # 共享配置的 Group
# refresh: true # 是否动态刷新
# extension-configs: # 拓展配置
# - data-id:
# group:
# refresh: true
# HTTP Server 相关配置
server:
port: 8083 # 服务器端口,默认为 8080
servlet:
context-path: /trade-order # 应用的访问路径,默认为 /
undertow:
threads:
# 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接,默认设置每个 CPU 核心一个线程
# 不要设置过大,如果过大,启动项目会报错:打开文件数过多
io: 16
# 阻塞任务线程池,当执行类似 Servlet 请求阻塞 IO 操作,Undertow 会从这个线程池中取得线程
# 它的值设置取决于系统线程执行任务的阻塞系数,默认值是 IO 线程数 * 8
worker: 256
buffer-size: 1024 # 每块 buffer 的大小,越小空间占用越小,一旦设置过小,对于大的请求则会打开更多 buffer,所以建议不要设置过小
direct-buffers: true # 是否分配的直接内存
# MyBatis Plus 相关配置
mybatis-plus:
# Mapper XML 文件的路径
mapper-locations: classpath*:mapper/*.xml
# 类型别名扫描包,多个package用逗号或者分号分隔
type-aliases-package: com.stock.trade.order.dal.dataobject
# 全局配置
global-config:
db-config:
# 全局默认主键类型
id-type: ASSIGN_ID
# 逻辑删除配置
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: true # 逻辑已删除值(默认为 1)
logic-not-delete-value: false # 逻辑未删除值(默认为 0)
banner: false # 是否关闭 MyBatis Plus 的 Banner
# MyBatis Plus 的具体配置
configuration:
# 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。
map-underscore-to-camel-case: true
# 对于数据库中值为 null 的字段,默认情况下,MyBatis 在返回的 Map 中并不会包含这个字段的键值对。
# 通过设置 callSettersOnNulls 为 trueMyBatis 会在返回的 Map 中包含值为 null 的字段,并将其值设置为 null。
call-setters-on-nulls: true
# 对于数据库中值为 null 的字段,默认情况下,MyBatis 在返回的实体类对象中会将该字段设置为 null。
# 通过设置 default-enum-type-handler 为 org.apache.ibatis.type.EnumOrdinalTypeHandlerMyBatis 会将枚举类型的字段值转换为其序数(ordinal)进行存储和查询。
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
# Spring Doc 相关配置
springdoc:
# Knife4j 配置
knife4j:
enable: true # 是否开启 Knife4j,默认为 false
setting:
language: zh_CN # API 列表的语言,可选 zh_CN、en_US
# API 分组
group-configs:
- group: default
paths-to-match: /**
packages-to-scan: com.stock.trade.order.controller # 分组的 Controller 包路径
# Actuator 相关配置
management:
# /actuator/health 端点配置
health:
# 默认情况下,/actuator/health 只会展示整体的应用健康情况,通过设置为 SHOW_ALWAYS 后,可以展示详细的应用健康情况,例如:磁盘、数据库等。
show-details: ALWAYS
# /actuator/metrics 端点配置
metrics:
# /actuator/metrics 端点,默认情况下不会展示 tag 标签。通过设置为 true 后,可以展示 tag 标签,更加清晰。
tags:
application: ${spring.application.name}
# /actuator 端点配置
endpoints:
# Web 端点的暴露范围,设置为 * 时,表示暴露所有端点。
web:
exposure:
include: '*'
# JMX 端点的暴露范围,设置为 * 时,表示暴露所有端点。
jmx:
exposure:
include: '*'
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 应用名称 -->
<springProperty name="APP_NAME" scope="context" source="spring.application.name" defaultValue="trade-order"/>
<!-- 日志路径 -->
<property name="LOG_PATH" value="logs/${APP_NAME}"/>
<!-- 日志格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC_FILE_INFO" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="FILE_INFO"/>
</appender>
<appender name="ASYNC_FILE_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="FILE_ERROR"/>
</appender>
<!-- Spring Profile 相关配置 -->
<springProfile name="dev,test">
<logger name="com.stock.trade.order" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE_INFO"/>
<appender-ref ref="ASYNC_FILE_ERROR"/>
</root>
</springProfile>
<springProfile name="prod">
<logger name="com.stock.trade.order" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE_INFO"/>
<appender-ref ref="ASYNC_FILE_ERROR"/>
</root>
</springProfile>
</configuration>