feat:使用liteflow
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 库存操作类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum InventoryOperationTypeEnum {
|
||||
|
||||
/**
|
||||
* 锁定
|
||||
*/
|
||||
LOCK("LOCK", "锁定"),
|
||||
|
||||
/**
|
||||
* 解锁
|
||||
*/
|
||||
UNLOCK("UNLOCK", "解锁"),
|
||||
|
||||
/**
|
||||
* 扣减
|
||||
*/
|
||||
DEDUCT("DEDUCT", "扣减"),
|
||||
|
||||
/**
|
||||
* 释放
|
||||
*/
|
||||
RELEASE("RELEASE", "释放");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static InventoryOperationTypeEnum fromCode(String code) {
|
||||
for (InventoryOperationTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的库存操作类型: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 运营模式枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OperationModeEnum {
|
||||
|
||||
/**
|
||||
* 固定班次
|
||||
*/
|
||||
FIXED("FIXED", "固定班次"),
|
||||
|
||||
/**
|
||||
* 滚动发车
|
||||
*/
|
||||
ROLLING("ROLLING", "滚动发车");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static OperationModeEnum fromCode(String code) {
|
||||
for (OperationModeEnum mode : values()) {
|
||||
if (mode.getCode().equals(code)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的运营模式: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 订单状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OrderStatusEnum {
|
||||
|
||||
/**
|
||||
* 待支付
|
||||
*/
|
||||
UNPAID("UNPAID", "待支付"),
|
||||
|
||||
/**
|
||||
* 已支付
|
||||
*/
|
||||
PAID("PAID", "已支付"),
|
||||
|
||||
/**
|
||||
* 使用中
|
||||
*/
|
||||
USING("USING", "使用中"),
|
||||
|
||||
/**
|
||||
* 已完成
|
||||
*/
|
||||
COMPLETED("COMPLETED", "已完成"),
|
||||
|
||||
/**
|
||||
* 已取消
|
||||
*/
|
||||
CANCELLED("CANCELLED", "已取消"),
|
||||
|
||||
/**
|
||||
* 已退款
|
||||
*/
|
||||
REFUNDED("REFUNDED", "已退款");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static OrderStatusEnum fromCode(String code) {
|
||||
for (OrderStatusEnum status : values()) {
|
||||
if (status.getCode().equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的订单状态: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 订单类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OrderTypeEnum {
|
||||
|
||||
/**
|
||||
* 固定班次
|
||||
*/
|
||||
FIXED("FIXED", "固定班次"),
|
||||
|
||||
/**
|
||||
* 滚动发车
|
||||
*/
|
||||
ROLLING("ROLLING", "滚动发车");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static OrderTypeEnum fromCode(String code) {
|
||||
for (OrderTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的订单类型: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 车票状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TicketStatusEnum {
|
||||
|
||||
/**
|
||||
* 有效
|
||||
*/
|
||||
VALID("VALID", "有效"),
|
||||
|
||||
/**
|
||||
* 已使用
|
||||
*/
|
||||
USED("USED", "已使用"),
|
||||
|
||||
/**
|
||||
* 已过期
|
||||
*/
|
||||
EXPIRED("EXPIRED", "已过期"),
|
||||
|
||||
/**
|
||||
* 已退款
|
||||
*/
|
||||
REFUNDED("REFUNDED", "已退款");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static TicketStatusEnum fromCode(String code) {
|
||||
for (TicketStatusEnum status : values()) {
|
||||
if (status.getCode().equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的车票状态: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package pers.amos.mall.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 票种类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TicketTypeEnum {
|
||||
|
||||
/**
|
||||
* 成人票
|
||||
*/
|
||||
ADULT("ADULT", "成人票"),
|
||||
|
||||
/**
|
||||
* 学生票
|
||||
*/
|
||||
STUDENT("STUDENT", "学生票"),
|
||||
|
||||
/**
|
||||
* 儿童票
|
||||
*/
|
||||
CHILD("CHILD", "儿童票");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
public static TicketTypeEnum fromCode(String code) {
|
||||
for (TicketTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的票种类型: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package pers.amos.mall.perform.service;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import pers.amos.mall.api.dto.TicketDTO;
|
||||
import pers.amos.mall.api.dto.TicketGenerateDTO;
|
||||
import pers.amos.mall.api.service.TicketService;
|
||||
import pers.amos.mall.common.enums.TicketStatusEnum;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.perform.dal.dataobject.Ticket;
|
||||
import pers.amos.mall.perform.dal.repository.TicketRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 车票服务Dubbo实现
|
||||
*/
|
||||
@Slf4j
|
||||
@DubboService
|
||||
public class TicketServiceImpl implements TicketService {
|
||||
|
||||
@Autowired
|
||||
private TicketRepository ticketRepository;
|
||||
|
||||
@Override
|
||||
public Result<List<TicketDTO>> generateTickets(TicketGenerateDTO dto) {
|
||||
try {
|
||||
List<Ticket> tickets = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < dto.getQuantity(); i++) {
|
||||
Ticket ticket = new Ticket();
|
||||
ticket.setTicketNo(generateTicketNo());
|
||||
ticket.setOrderNo(dto.getOrderNo());
|
||||
ticket.setUserNo(dto.getUserNo());
|
||||
ticket.setRouteCode(dto.getRouteCode());
|
||||
ticket.setOrderType(dto.getOrderType());
|
||||
ticket.setScheduleCode(dto.getScheduleCode());
|
||||
ticket.setRollingScheduleCode(dto.getRollingScheduleCode());
|
||||
ticket.setTicketType(dto.getTicketType());
|
||||
|
||||
// 生成二维码内容(这里简单使用票号,实际应该加密)
|
||||
ticket.setQrCode(encryptTicketNo(ticket.getTicketNo()));
|
||||
|
||||
ticket.setStatus(TicketStatusEnum.VALID.getCode());
|
||||
ticket.setIssueTime(LocalDateTime.now());
|
||||
ticket.setCreateTime(LocalDateTime.now());
|
||||
ticket.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
tickets.add(ticket);
|
||||
}
|
||||
|
||||
// 批量保存
|
||||
ticketRepository.saveBatch(tickets);
|
||||
|
||||
log.info("车票生成成功, orderNo={}, quantity={}", dto.getOrderNo(), dto.getQuantity());
|
||||
|
||||
// 转换为DTO
|
||||
List<TicketDTO> ticketDTOs = tickets.stream()
|
||||
.map(this::convertToDTO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(ticketDTOs);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("车票生成失败, orderNo={}", dto.getOrderNo(), e);
|
||||
return Result.fail("TICKET_GENERATE_ERROR", "车票生成失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成车票号
|
||||
* 格式:TKT + yyyyMMddHHmmssSSS + 6位随机数
|
||||
*/
|
||||
private String generateTicketNo() {
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
|
||||
String random = RandomUtil.randomNumbers(6);
|
||||
return "TKT" + timestamp + random;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密车票号生成二维码内容
|
||||
* 这里简化处理,实际应该使用AES等加密算法
|
||||
*/
|
||||
private String encryptTicketNo(String ticketNo) {
|
||||
// TODO: 实际应该使用加密算法
|
||||
return "QR_" + ticketNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为DTO
|
||||
*/
|
||||
private TicketDTO convertToDTO(Ticket ticket) {
|
||||
TicketDTO dto = new TicketDTO();
|
||||
BeanUtils.copyProperties(ticket, dto);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ spring:
|
||||
username: root
|
||||
password: root
|
||||
|
||||
# Jackson配置
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
@@ -45,13 +44,10 @@ dubbo:
|
||||
registry:
|
||||
address: nacos://localhost:8848
|
||||
scan:
|
||||
base-packages: pers.amos.mall.perform.api
|
||||
base-packages: pers.amos.mall.perform.service
|
||||
|
||||
# RocketMQ配置
|
||||
rocketmq:
|
||||
name-server: localhost:9876
|
||||
producer:
|
||||
group: perform-producer-group
|
||||
consumer:
|
||||
group: perform-consumer-group
|
||||
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
package pers.amos.mall.route.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import pers.amos.mall.api.dto.InventoryCheckDTO;
|
||||
import pers.amos.mall.api.dto.InventoryOperationDTO;
|
||||
import pers.amos.mall.api.service.RouteService;
|
||||
import pers.amos.mall.common.enums.InventoryOperationTypeEnum;
|
||||
import pers.amos.mall.common.enums.OrderTypeEnum;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.route.dal.dataobject.FixedSchedule;
|
||||
import pers.amos.mall.route.dal.dataobject.InventoryLog;
|
||||
import pers.amos.mall.route.dal.dataobject.RollingSchedule;
|
||||
import pers.amos.mall.route.dal.repository.FixedScheduleRepository;
|
||||
import pers.amos.mall.route.dal.repository.InventoryLogRepository;
|
||||
import pers.amos.mall.route.dal.repository.RollingScheduleRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 线路服务Dubbo实现
|
||||
*/
|
||||
@Slf4j
|
||||
@DubboService
|
||||
public class RouteServiceImpl implements RouteService {
|
||||
|
||||
@Autowired
|
||||
private FixedScheduleRepository fixedScheduleRepository;
|
||||
|
||||
@Autowired
|
||||
private RollingScheduleRepository rollingScheduleRepository;
|
||||
|
||||
@Autowired
|
||||
private InventoryLogRepository inventoryLogRepository;
|
||||
|
||||
@Override
|
||||
public Result<Boolean> checkInventory(InventoryCheckDTO dto) {
|
||||
try {
|
||||
if (OrderTypeEnum.FIXED.getCode().equals(dto.getOrderType())) {
|
||||
return checkFixedScheduleInventory(dto);
|
||||
} else {
|
||||
return checkRollingScheduleInventory(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("检查库存失败", e);
|
||||
return Result.fail("INVENTORY_CHECK_ERROR", "检查库存失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> lockInventory(InventoryOperationDTO dto) {
|
||||
try {
|
||||
if (OrderTypeEnum.FIXED.getCode().equals(dto.getOrderType())) {
|
||||
return lockFixedScheduleInventory(dto);
|
||||
} else {
|
||||
return lockRollingScheduleInventory(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("锁定库存失败, orderNo={}", dto.getOrderNo(), e);
|
||||
return Result.fail("INVENTORY_LOCK_ERROR", "锁定库存失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> deductInventory(InventoryOperationDTO dto) {
|
||||
try {
|
||||
if (OrderTypeEnum.FIXED.getCode().equals(dto.getOrderType())) {
|
||||
return deductFixedScheduleInventory(dto);
|
||||
} else {
|
||||
return deductRollingScheduleInventory(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("扣减库存失败, orderNo={}", dto.getOrderNo(), e);
|
||||
return Result.fail("INVENTORY_DEDUCT_ERROR", "扣减库存失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> releaseInventory(InventoryOperationDTO dto) {
|
||||
try {
|
||||
if (OrderTypeEnum.FIXED.getCode().equals(dto.getOrderType())) {
|
||||
return releaseFixedScheduleInventory(dto);
|
||||
} else {
|
||||
return releaseRollingScheduleInventory(dto);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("释放库存失败, orderNo={}", dto.getOrderNo(), e);
|
||||
return Result.fail("INVENTORY_RELEASE_ERROR", "释放库存失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 固定班次库存操作 ====================
|
||||
|
||||
private Result<Boolean> checkFixedScheduleInventory(InventoryCheckDTO dto) {
|
||||
FixedSchedule schedule = fixedScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "班次不存在");
|
||||
}
|
||||
|
||||
if (schedule.getAvailableSeats() < dto.getQuantity()) {
|
||||
return Result.fail("INSUFFICIENT_INVENTORY", "库存不足");
|
||||
}
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> lockFixedScheduleInventory(InventoryOperationDTO dto) {
|
||||
FixedSchedule schedule = fixedScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeQty = schedule.getAvailableSeats();
|
||||
|
||||
// 乐观锁扣减可用库存
|
||||
boolean success = fixedScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
.eq(FixedSchedule::getVersion, schedule.getVersion())
|
||||
.setSql("available_seats = available_seats - " + dto.getQuantity())
|
||||
.set(FixedSchedule::getVersion, schedule.getVersion() + 1)
|
||||
.ge(FixedSchedule::getAvailableSeats, dto.getQuantity())
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return Result.fail("INVENTORY_LOCK_FAILED", "库存锁定失败,可能库存不足或并发冲突");
|
||||
}
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.FIXED.getCode(), dto.getScheduleCode(), null, InventoryOperationTypeEnum.LOCK.getCode(),
|
||||
dto.getQuantity(), beforeQty, beforeQty - dto.getQuantity(),
|
||||
dto.getOrderNo(), "锁定库存");
|
||||
|
||||
log.info("固定班次库存锁定成功, scheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> deductFixedScheduleInventory(InventoryOperationDTO dto) {
|
||||
// 固定班次在锁定时已经扣减了available_seats,这里只需要增加sold_seats
|
||||
FixedSchedule schedule = fixedScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeSold = schedule.getSoldSeats();
|
||||
|
||||
fixedScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
.setSql("sold_seats = sold_seats + " + dto.getQuantity())
|
||||
);
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.FIXED.getCode(), dto.getScheduleCode(), null, InventoryOperationTypeEnum.DEDUCT.getCode(),
|
||||
dto.getQuantity(), beforeSold, beforeSold + dto.getQuantity(),
|
||||
dto.getOrderNo(), "扣减库存(支付成功)");
|
||||
|
||||
log.info("固定班次库存扣减成功, scheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> releaseFixedScheduleInventory(InventoryOperationDTO dto) {
|
||||
FixedSchedule schedule = fixedScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeQty = schedule.getAvailableSeats();
|
||||
|
||||
// 释放库存
|
||||
fixedScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(FixedSchedule.class)
|
||||
.eq(FixedSchedule::getScheduleCode, dto.getScheduleCode())
|
||||
.setSql("available_seats = available_seats + " + dto.getQuantity())
|
||||
);
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.FIXED.getCode(), dto.getScheduleCode(), null, InventoryOperationTypeEnum.RELEASE.getCode(),
|
||||
dto.getQuantity(), beforeQty, beforeQty + dto.getQuantity(),
|
||||
dto.getOrderNo(), "释放库存(订单取消/超时)");
|
||||
|
||||
log.info("固定班次库存释放成功, scheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
// ==================== 滚动班次库存操作 ====================
|
||||
|
||||
private Result<Boolean> checkRollingScheduleInventory(InventoryCheckDTO dto) {
|
||||
RollingSchedule schedule = rollingScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "滚动班次不存在");
|
||||
}
|
||||
|
||||
if (schedule.getAvailableSeats() < dto.getQuantity()) {
|
||||
return Result.fail("INSUFFICIENT_INVENTORY", "库存不足");
|
||||
}
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> lockRollingScheduleInventory(InventoryOperationDTO dto) {
|
||||
RollingSchedule schedule = rollingScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "滚动班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeQty = schedule.getAvailableSeats();
|
||||
|
||||
// 乐观锁扣减共享库存池
|
||||
boolean success = rollingScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
.eq(RollingSchedule::getVersion, schedule.getVersion())
|
||||
.setSql("available_seats = available_seats - " + dto.getQuantity())
|
||||
.set(RollingSchedule::getVersion, schedule.getVersion() + 1)
|
||||
.ge(RollingSchedule::getAvailableSeats, dto.getQuantity())
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return Result.fail("INVENTORY_LOCK_FAILED", "库存锁定失败,可能库存不足或并发冲突");
|
||||
}
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.ROLLING.getCode(), null, dto.getRollingScheduleCode(), InventoryOperationTypeEnum.LOCK.getCode(),
|
||||
dto.getQuantity(), beforeQty, beforeQty - dto.getQuantity(),
|
||||
dto.getOrderNo(), "锁定共享库存池");
|
||||
|
||||
log.info("滚动班次库存锁定成功, rollingScheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getRollingScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> deductRollingScheduleInventory(InventoryOperationDTO dto) {
|
||||
RollingSchedule schedule = rollingScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "滚动班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeSold = schedule.getSoldSeats();
|
||||
|
||||
rollingScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
.setSql("sold_seats = sold_seats + " + dto.getQuantity())
|
||||
);
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.ROLLING.getCode(), null, dto.getRollingScheduleCode(), InventoryOperationTypeEnum.DEDUCT.getCode(),
|
||||
dto.getQuantity(), beforeSold, beforeSold + dto.getQuantity(),
|
||||
dto.getOrderNo(), "扣减库存(支付成功)");
|
||||
|
||||
log.info("滚动班次库存扣减成功, rollingScheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getRollingScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
private Result<Boolean> releaseRollingScheduleInventory(InventoryOperationDTO dto) {
|
||||
RollingSchedule schedule = rollingScheduleRepository.getOne(
|
||||
Wrappers.lambdaQuery(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
);
|
||||
|
||||
if (schedule == null) {
|
||||
return Result.fail("SCHEDULE_NOT_FOUND", "滚动班次不存在");
|
||||
}
|
||||
|
||||
Integer beforeQty = schedule.getAvailableSeats();
|
||||
|
||||
// 释放库存
|
||||
rollingScheduleRepository.update(
|
||||
Wrappers.lambdaUpdate(RollingSchedule.class)
|
||||
.eq(RollingSchedule::getRollingScheduleCode, dto.getRollingScheduleCode())
|
||||
.setSql("available_seats = available_seats + " + dto.getQuantity())
|
||||
);
|
||||
|
||||
// 记录库存日志
|
||||
saveInventoryLog(OrderTypeEnum.ROLLING.getCode(), null, dto.getRollingScheduleCode(), InventoryOperationTypeEnum.RELEASE.getCode(),
|
||||
dto.getQuantity(), beforeQty, beforeQty + dto.getQuantity(),
|
||||
dto.getOrderNo(), "释放库存(订单取消/超时)");
|
||||
|
||||
log.info("滚动班次库存释放成功, rollingScheduleCode={}, quantity={}, orderNo={}",
|
||||
dto.getRollingScheduleCode(), dto.getQuantity(), dto.getOrderNo());
|
||||
|
||||
return Result.success(true);
|
||||
}
|
||||
|
||||
// ==================== 库存日志记录 ====================
|
||||
|
||||
private void saveInventoryLog(String inventoryType, String scheduleCode,
|
||||
String rollingScheduleCode, String operationType,
|
||||
Integer quantity, Integer beforeQty, Integer afterQty,
|
||||
String orderNo, String remark) {
|
||||
InventoryLog log = new InventoryLog();
|
||||
log.setInventoryType(inventoryType);
|
||||
log.setScheduleCode(scheduleCode);
|
||||
log.setRollingScheduleCode(rollingScheduleCode);
|
||||
log.setOperationType(operationType);
|
||||
log.setQuantity(quantity);
|
||||
log.setBeforeQty(beforeQty);
|
||||
log.setAfterQty(afterQty);
|
||||
log.setOrderNo(orderNo);
|
||||
log.setRemark(remark);
|
||||
log.setCreateTime(LocalDateTime.now());
|
||||
|
||||
inventoryLogRepository.save(log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ dubbo:
|
||||
registry:
|
||||
address: nacos://localhost:8848
|
||||
scan:
|
||||
base-packages: pers.amos.mall.route.api
|
||||
base-packages: pers.amos.mall.route.service
|
||||
|
||||
# RocketMQ配置
|
||||
rocketmq:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package pers.amos.mall.trade.controller;
|
||||
|
||||
import com.yomahub.liteflow.core.FlowExecutor;
|
||||
import com.yomahub.liteflow.flow.LiteflowResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.api.dto.OrderResultDTO;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
/**
|
||||
* 订单Controller
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/order")
|
||||
public class OrderController {
|
||||
|
||||
@Autowired
|
||||
private FlowExecutor flowExecutor;
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
public Result<OrderResultDTO> createOrder(@RequestBody OrderCreateDTO dto) {
|
||||
log.info("收到创建订单请求, userNo={}, routeCode={}, orderType={}, quantity={}",
|
||||
dto.getUserNo(), dto.getRouteCode(), dto.getOrderType(), dto.getQuantity());
|
||||
|
||||
try {
|
||||
// 创建上下文
|
||||
OrderContext context = new OrderContext();
|
||||
context.setOrderCreateDTO(dto);
|
||||
|
||||
// 执行流程
|
||||
LiteflowResponse response = flowExecutor.execute2Resp("orderChain", null, context);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
OrderResultDTO resultDTO = context.getOrderResultDTO();
|
||||
log.info("订单创建成功, orderNo={}", resultDTO.getOrderNo());
|
||||
return Result.success(resultDTO);
|
||||
} else {
|
||||
log.error("订单创建失败: {}", response.getMessage());
|
||||
return Result.fail("ORDER_CREATE_FAIL", response.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("订单创建异常", e);
|
||||
return Result.fail("ORDER_CREATE_ERROR", "订单创建失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package pers.amos.mall.trade.liteflow.context;
|
||||
|
||||
import lombok.Data;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.api.dto.OrderResultDTO;
|
||||
import pers.amos.mall.api.dto.TicketDTO;
|
||||
import pers.amos.mall.trade.dal.dataobject.Order;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下单流程上下文
|
||||
*/
|
||||
@Data
|
||||
public class OrderContext {
|
||||
|
||||
/**
|
||||
* 输入:创建订单DTO
|
||||
*/
|
||||
private OrderCreateDTO orderCreateDTO;
|
||||
|
||||
/**
|
||||
* 中间数据:订单实体
|
||||
*/
|
||||
private Order order;
|
||||
|
||||
/**
|
||||
* 中间数据:车票列表
|
||||
*/
|
||||
private List<TicketDTO> tickets;
|
||||
|
||||
/**
|
||||
* 输出:订单结果DTO
|
||||
*/
|
||||
private OrderResultDTO orderResultDTO;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pers.amos.mall.api.dto.OrderResultDTO;
|
||||
import pers.amos.mall.trade.dal.dataobject.Order;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
/**
|
||||
* 构建返回结果节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("buildResult")
|
||||
public class BuildResultNode extends NodeComponent {
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
Order order = context.getOrder();
|
||||
|
||||
log.info("开始构建返回结果, orderNo={}", order.getOrderNo());
|
||||
|
||||
// 构建返回DTO
|
||||
OrderResultDTO resultDTO = new OrderResultDTO();
|
||||
BeanUtils.copyProperties(order, resultDTO);
|
||||
resultDTO.setTickets(context.getTickets());
|
||||
|
||||
context.setOrderResultDTO(resultDTO);
|
||||
|
||||
log.info("返回结果构建成功, orderNo={}", order.getOrderNo());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pers.amos.mall.api.dto.InventoryCheckDTO;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.api.service.RouteService;
|
||||
import pers.amos.mall.common.enums.OrderTypeEnum;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
/**
|
||||
* 检查库存节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("checkInventory")
|
||||
public class CheckInventoryNode extends NodeComponent {
|
||||
|
||||
@DubboReference
|
||||
private RouteService routeService;
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
OrderCreateDTO dto = context.getOrderCreateDTO();
|
||||
|
||||
log.info("开始检查库存, orderType={}, quantity={}", dto.getOrderType(), dto.getQuantity());
|
||||
|
||||
// 构建库存检查DTO
|
||||
InventoryCheckDTO checkDTO = new InventoryCheckDTO();
|
||||
checkDTO.setOrderType(dto.getOrderType());
|
||||
checkDTO.setScheduleCode(dto.getScheduleCode());
|
||||
checkDTO.setRollingScheduleCode(generateRollingScheduleCode(dto));
|
||||
checkDTO.setQuantity(dto.getQuantity());
|
||||
|
||||
// Dubbo调用Route服务检查库存
|
||||
Result<Boolean> result = routeService.checkInventory(checkDTO);
|
||||
|
||||
if (!result.isSuccess() || !Boolean.TRUE.equals(result.getData())) {
|
||||
throw new RuntimeException("库存不足:" + result.getErrorDesc());
|
||||
}
|
||||
|
||||
log.info("库存检查通过, orderType={}, quantity={}", dto.getOrderType(), dto.getQuantity());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成滚动班次编号
|
||||
* 格式:线路编码 + 日期 + RS
|
||||
*/
|
||||
private String generateRollingScheduleCode(OrderCreateDTO dto) {
|
||||
if (OrderTypeEnum.ROLLING.getCode().equals(dto.getOrderType())) {
|
||||
return dto.getRouteCode() + "-" + dto.getScheduleDate().replace("-", "") + "-RS";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.common.enums.OrderStatusEnum;
|
||||
import pers.amos.mall.common.enums.OrderTypeEnum;
|
||||
import pers.amos.mall.trade.dal.dataobject.Order;
|
||||
import pers.amos.mall.trade.dal.repository.OrderRepository;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创建订单节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("createOrder")
|
||||
public class CreateOrderNode extends NodeComponent {
|
||||
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
OrderCreateDTO dto = context.getOrderCreateDTO();
|
||||
|
||||
// 从上下文获取订单号(临时存储在errorMessage中)
|
||||
String orderNo = context.getErrorMessage();
|
||||
|
||||
log.info("开始创建订单, orderNo={}", orderNo);
|
||||
|
||||
Order order = new Order();
|
||||
order.setOrderNo(orderNo);
|
||||
order.setUserNo(dto.getUserNo());
|
||||
order.setRouteCode(dto.getRouteCode());
|
||||
order.setOrderType(dto.getOrderType());
|
||||
order.setScheduleCode(dto.getScheduleCode());
|
||||
|
||||
// 如果是滚动发车,设置滚动班次编号
|
||||
if (OrderTypeEnum.ROLLING.getCode().equals(dto.getOrderType())) {
|
||||
String rollingScheduleCode = dto.getRouteCode() + "-" +
|
||||
dto.getScheduleDate().replace("-", "") + "-RS";
|
||||
order.setRollingScheduleCode(rollingScheduleCode);
|
||||
}
|
||||
|
||||
order.setTicketType(dto.getTicketType());
|
||||
order.setQuantity(dto.getQuantity());
|
||||
|
||||
// TODO: 根据线路和票种查询价格,这里暂时写死
|
||||
BigDecimal unitPrice = new BigDecimal("50.00");
|
||||
order.setUnitPrice(unitPrice);
|
||||
order.setTotalAmount(unitPrice.multiply(new BigDecimal(dto.getQuantity())));
|
||||
|
||||
order.setOrderStatus(OrderStatusEnum.UNPAID.getCode());
|
||||
order.setCreateTime(LocalDateTime.now());
|
||||
order.setExpireTime(LocalDateTime.now().plusMinutes(15)); // 15分钟支付超时
|
||||
order.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
orderRepository.save(order);
|
||||
|
||||
// 将订单保存到上下文
|
||||
context.setOrder(order);
|
||||
|
||||
log.info("订单创建成功, orderNo={}, totalAmount={}", orderNo, order.getTotalAmount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pers.amos.mall.api.dto.TicketDTO;
|
||||
import pers.amos.mall.api.dto.TicketGenerateDTO;
|
||||
import pers.amos.mall.api.service.TicketService;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.trade.dal.dataobject.Order;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生成车票节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("generateTickets")
|
||||
public class GenerateTicketsNode extends NodeComponent {
|
||||
|
||||
@DubboReference
|
||||
private TicketService ticketService;
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
Order order = context.getOrder();
|
||||
|
||||
log.info("开始生成车票, orderNo={}, quantity={}", order.getOrderNo(), order.getQuantity());
|
||||
|
||||
// 构建生成车票DTO
|
||||
TicketGenerateDTO generateDTO = new TicketGenerateDTO();
|
||||
generateDTO.setOrderNo(order.getOrderNo());
|
||||
generateDTO.setUserNo(order.getUserNo());
|
||||
generateDTO.setRouteCode(order.getRouteCode());
|
||||
generateDTO.setOrderType(order.getOrderType());
|
||||
generateDTO.setScheduleCode(order.getScheduleCode());
|
||||
generateDTO.setRollingScheduleCode(order.getRollingScheduleCode());
|
||||
generateDTO.setTicketType(order.getTicketType());
|
||||
generateDTO.setQuantity(order.getQuantity());
|
||||
|
||||
// Dubbo调用Perform服务生成车票
|
||||
Result<List<TicketDTO>> result = ticketService.generateTickets(generateDTO);
|
||||
|
||||
if (!result.isSuccess() || result.getData() == null) {
|
||||
throw new RuntimeException("车票生成失败:" + result.getErrorDesc());
|
||||
}
|
||||
|
||||
// 将车票保存到上下文
|
||||
context.setTickets(result.getData());
|
||||
|
||||
log.info("车票生成成功, orderNo={}, ticketCount={}",
|
||||
order.getOrderNo(), result.getData().size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pers.amos.mall.api.dto.InventoryOperationDTO;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.api.service.RouteService;
|
||||
import pers.amos.mall.common.enums.OrderTypeEnum;
|
||||
import pers.amos.mall.common.response.Result;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 锁定库存节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("lockInventory")
|
||||
public class LockInventoryNode extends NodeComponent {
|
||||
|
||||
@DubboReference
|
||||
private RouteService routeService;
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
OrderCreateDTO dto = context.getOrderCreateDTO();
|
||||
|
||||
// 生成订单号
|
||||
String orderNo = generateOrderNo();
|
||||
|
||||
log.info("开始锁定库存, orderNo={}, orderType={}, quantity={}",
|
||||
orderNo, dto.getOrderType(), dto.getQuantity());
|
||||
|
||||
// 构建库存操作DTO
|
||||
InventoryOperationDTO operationDTO = new InventoryOperationDTO();
|
||||
operationDTO.setOrderNo(orderNo);
|
||||
operationDTO.setOrderType(dto.getOrderType());
|
||||
operationDTO.setScheduleCode(dto.getScheduleCode());
|
||||
operationDTO.setRollingScheduleCode(generateRollingScheduleCode(dto));
|
||||
operationDTO.setQuantity(dto.getQuantity());
|
||||
|
||||
// Dubbo调用Route服务锁定库存
|
||||
Result<Boolean> result = routeService.lockInventory(operationDTO);
|
||||
|
||||
if (!result.isSuccess() || !Boolean.TRUE.equals(result.getData())) {
|
||||
throw new RuntimeException("库存锁定失败:" + result.getErrorDesc());
|
||||
}
|
||||
|
||||
// 将订单号和滚动班次编号保存到上下文
|
||||
dto.setScheduleCode(dto.getScheduleCode());
|
||||
if (OrderTypeEnum.ROLLING.getCode().equals(dto.getOrderType())) {
|
||||
// 将生成的滚动班次编号保存到DTO,供后续节点使用
|
||||
context.setErrorMessage(operationDTO.getRollingScheduleCode()); // 临时存储
|
||||
}
|
||||
|
||||
log.info("库存锁定成功, orderNo={}", orderNo);
|
||||
|
||||
// 将订单号保存到上下文
|
||||
context.setErrorMessage(orderNo); // 临时存储订单号
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单号
|
||||
* 格式:ORD + yyyyMMddHHmmssSSS + 6位随机数
|
||||
*/
|
||||
private String generateOrderNo() {
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
|
||||
String random = RandomUtil.randomNumbers(6);
|
||||
return "ORD" + timestamp + random;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成滚动班次编号
|
||||
*/
|
||||
private String generateRollingScheduleCode(OrderCreateDTO dto) {
|
||||
if (OrderTypeEnum.ROLLING.getCode().equals(dto.getOrderType())) {
|
||||
return dto.getRouteCode() + "-" + dto.getScheduleDate().replace("-", "") + "-RS";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package pers.amos.mall.trade.liteflow.node;
|
||||
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import pers.amos.mall.api.dto.OrderCreateDTO;
|
||||
import pers.amos.mall.common.enums.OrderTypeEnum;
|
||||
import pers.amos.mall.trade.liteflow.context.OrderContext;
|
||||
|
||||
/**
|
||||
* 参数校验节点
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("validateParams")
|
||||
public class ValidateParamsNode extends NodeComponent {
|
||||
|
||||
@Override
|
||||
public void process() throws Exception {
|
||||
OrderContext context = this.getContextBean(OrderContext.class);
|
||||
OrderCreateDTO dto = context.getOrderCreateDTO();
|
||||
|
||||
log.info("开始参数校验, userNo={}", dto.getUserNo());
|
||||
|
||||
// 校验基本参数
|
||||
if (!StringUtils.hasText(dto.getUserNo())) {
|
||||
throw new IllegalArgumentException("用户编号不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(dto.getRouteCode())) {
|
||||
throw new IllegalArgumentException("线路编码不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(dto.getOrderType())) {
|
||||
throw new IllegalArgumentException("订单类型不能为空");
|
||||
}
|
||||
|
||||
if (dto.getQuantity() == null || dto.getQuantity() <= 0) {
|
||||
throw new IllegalArgumentException("购票数量必须大于0");
|
||||
}
|
||||
|
||||
// 根据订单类型校验特定参数
|
||||
if (OrderTypeEnum.FIXED.getCode().equals(dto.getOrderType())) {
|
||||
if (!StringUtils.hasText(dto.getScheduleCode())) {
|
||||
throw new IllegalArgumentException("固定班次编号不能为空");
|
||||
}
|
||||
} else if (OrderTypeEnum.ROLLING.getCode().equals(dto.getOrderType())) {
|
||||
if (!StringUtils.hasText(dto.getScheduleDate())) {
|
||||
throw new IllegalArgumentException("滚动班次日期不能为空");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("订单类型无效");
|
||||
}
|
||||
|
||||
log.info("参数校验通过, userNo={}", dto.getUserNo());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ spring:
|
||||
username: root
|
||||
password: root
|
||||
|
||||
# Jackson配置
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
@@ -44,14 +43,18 @@ dubbo:
|
||||
port: 20882
|
||||
registry:
|
||||
address: nacos://localhost:8848
|
||||
scan:
|
||||
base-packages: pers.amos.mall.trade.api
|
||||
consumer:
|
||||
check: false
|
||||
timeout: 5000
|
||||
retries: 0
|
||||
|
||||
# RocketMQ配置
|
||||
rocketmq:
|
||||
name-server: localhost:9876
|
||||
producer:
|
||||
group: trade-producer-group
|
||||
consumer:
|
||||
group: trade-consumer-group
|
||||
|
||||
# LiteFlow配置
|
||||
liteflow:
|
||||
rule-source: liteflow/order-flow.el.xml
|
||||
enable: true
|
||||
|
||||
15
trade/src/main/resources/liteflow/order-flow.el.xml
Normal file
15
trade/src/main/resources/liteflow/order-flow.el.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<flow>
|
||||
<!-- 下单流程 -->
|
||||
<chain name="orderChain">
|
||||
THEN(
|
||||
validateParams,
|
||||
checkInventory,
|
||||
lockInventory,
|
||||
createOrder,
|
||||
generateTickets,
|
||||
buildResult
|
||||
);
|
||||
</chain>
|
||||
</flow>
|
||||
|
||||
Reference in New Issue
Block a user