From ab7e92d5154b5acf05699bd527c4d8b5fff10550 Mon Sep 17 00:00:00 2001
From: zyy <zyy@email.com>
Date: Sat, 06 Dec 2025 19:01:30 +0800
Subject: [PATCH] 新增ATS
---
trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java | 71 +++++++
trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java | 238 ++++++++++++++++++++++++++
trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java | 18 ++
trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java | 18 ++
trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java | 52 +++++
trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java | 56 ++++++
trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml | 22 ++
trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java | 17 +
8 files changed, 492 insertions(+), 0 deletions(-)
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java
new file mode 100644
index 0000000..9d88c28
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java
@@ -0,0 +1,52 @@
+package com.yami.trading.admin.controller.ats;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yami.trading.bean.ats.dto.StockAtsDto;
+import com.yami.trading.common.domain.Result;
+import com.yami.trading.service.ats.StockAtsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@RestController
+@CrossOrigin
+@Api(tags = "后台股票ATS")
+@RequestMapping("stockAts")
+@Slf4j
+public class AdminStockAtsController {
+
+ @Resource
+ StockAtsService stockAtsService;
+
+ /**
+ * @Description: 获取ATS列表
+ * @Param:
+ * @return:
+ */
+ @ApiOperation("获取ATS订单")
+ @PostMapping({"getListByAdmin.do"})
+ public Result<Page<StockAtsDto>> getListByAdmin(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "5") int pageSize,
+ @RequestParam(value = "keywords", required = false)String keywords) {
+ return stockAtsService.getListByAdmin(pageNum, pageSize, keywords);
+ }
+
+
+ @ApiOperation("ATS审核")
+ @PostMapping({"atsCheck.do"})
+ @ResponseBody
+ public Result atsCheck(@RequestParam(value = "id") String id,
+ @RequestParam(value = "checkType") Integer checkType,
+ @RequestParam(value = "stockCode", required = false) String stockCode,
+ @RequestParam(value = "closePrice", defaultValue = "0", required = false) double closePrice,
+ @RequestParam(value = "price",defaultValue = "0", required = false) double price) {
+ if((checkType == 1 && (id == null || stockCode == null) || (checkType == 2 && id == null))){
+ return Result.failed("参数不能为空");
+ }
+ return stockAtsService.atsCheck(id, checkType, stockCode, closePrice, price);
+ }
+
+}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java
new file mode 100644
index 0000000..8966bc1
--- /dev/null
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java
@@ -0,0 +1,71 @@
+package com.yami.trading.api.controller.ats;
+
+import com.yami.trading.bean.dz.ExchangeApplyOrderDz;
+import com.yami.trading.common.constants.Constants;
+import com.yami.trading.common.domain.Result;
+import com.yami.trading.common.exception.YamiShopBindException;
+import com.yami.trading.security.common.util.SecurityUtils;
+import com.yami.trading.service.ats.StockAtsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@RestController
+@CrossOrigin
+@Api(tags = "股票Ats")
+@RequestMapping("api/stockAts")
+@Slf4j
+public class ApiStockAtsController {
+
+ @Resource
+ StockAtsService stockAtsService;
+
+ private static final ThreadLocal<Boolean> orderCreated = ThreadLocal.withInitial(() -> false);
+ private final Lock lock = new ReentrantLock();
+
+ @ApiOperation("ATS订单列表")
+ @PostMapping({"getOrderList.do"})
+ @ResponseBody
+ public Result getOrderList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
+ @RequestParam(value = "pageSize", defaultValue = "5") int pageSize) {
+ String partyId = SecurityUtils.getCurrentUserId();
+ if (partyId == null || partyId.isEmpty()) {
+ throw new YamiShopBindException("请先登录");
+ }
+ return stockAtsService.getList(pageNum, pageSize, partyId);
+ }
+
+ @ApiOperation("ATS下单")
+ @GetMapping({"buyStockAts.do"})
+ @ResponseBody
+ public Result buyDz(@RequestParam(name = "price") double price) {
+ lock.lock();
+ try {
+ if (orderCreated.get()) {
+ throw new YamiShopBindException("当前交易人数过多,请稍后重试");
+ }
+ orderCreated.set(true);
+ String partyId = SecurityUtils.getCurrentUserId();
+ if (partyId == null || partyId.isEmpty()) {
+ throw new YamiShopBindException("请先登录");
+ }
+ return stockAtsService.buyAts(price, partyId);
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ } finally{
+ lock.unlock();
+ orderCreated.set(false);
+ }
+ throw new YamiShopBindException("订单异常,请稍后重试");
+ }
+
+
+
+
+
+}
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java b/trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java
new file mode 100644
index 0000000..2196d21
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java
@@ -0,0 +1,56 @@
+package com.yami.trading.bean.ats;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.yami.trading.common.domain.UUIDEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * ATS挂单
+ */
+@TableName("t_stock_ats")
+@Data
+public class StockAts extends UUIDEntity {
+
+ public final static String STATE_SUBMITTED = "submitted";
+ public final static String STATE_POSITION = "position";
+ public final static String STATE_FAILED = "failed";
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty("股票名称")
+ private String stockName;
+
+ @ApiModelProperty("股票代码")
+ private String stockCode;
+
+ private String stockSpell;
+
+ private String userId;
+
+ @ApiModelProperty("订单号")
+ private String orderNo;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date addTime;
+
+ @ApiModelProperty("购买金额")
+ private BigDecimal price;
+
+ @ApiModelProperty("买入价格")
+ private BigDecimal closePrice;
+
+ @ApiModelProperty("手续费")
+ private double fee;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date buyTime;
+
+ @ApiModelProperty("状态.submitted 已提交,position 已转持仓,failed 失败")
+ private String state = "submitted";
+
+}
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java b/trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java
new file mode 100644
index 0000000..0d84842
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java
@@ -0,0 +1,17 @@
+package com.yami.trading.bean.ats.dto;
+
+import com.yami.trading.bean.ats.StockAts;
+import com.yami.trading.bean.dz.StockDz;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class StockAtsDto extends StockAts {
+
+ @ApiModelProperty("UID")
+ private String userCode;
+
+ @ApiModelProperty("用户名")
+ private String userName;
+
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java b/trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java
new file mode 100644
index 0000000..66716b7
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java
@@ -0,0 +1,18 @@
+package com.yami.trading.dao.ats;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yami.trading.bean.ats.StockAts;
+import com.yami.trading.bean.ats.dto.StockAtsDto;
+import org.apache.ibatis.annotations.Param;
+
+
+public interface StockAtsMapper extends BaseMapper<StockAts> {
+
+
+ Page<StockAtsDto> getListByAdmin(Page page,
+ @Param("keyWords") String keyWords);
+
+
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java b/trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java
new file mode 100644
index 0000000..017a3d9
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java
@@ -0,0 +1,18 @@
+package com.yami.trading.service.ats;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yami.trading.bean.ats.StockAts;
+import com.yami.trading.bean.ats.dto.StockAtsDto;
+import com.yami.trading.common.domain.Result;
+
+public interface StockAtsService extends IService<StockAts> {
+
+ Result getList(int pageNum, int pageSize, String partyId);
+
+ Result buyAts(double price, String partyId);
+
+ Result<Page<StockAtsDto>> getListByAdmin(int pageNum, int pageSize, String keywords);
+
+ Result atsCheck(String id, Integer checkType, String stockCode, double closePrice, double price);
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java
new file mode 100644
index 0000000..5140ad2
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java
@@ -0,0 +1,238 @@
+package com.yami.trading.service.ats.impl;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yami.trading.bean.ats.StockAts;
+import com.yami.trading.bean.ats.dto.StockAtsDto;
+import com.yami.trading.bean.contract.domain.ContractApplyOrder;
+import com.yami.trading.bean.dz.ExchangeApplyOrderDz;
+import com.yami.trading.bean.exchange.ExchangeApplyOrder;
+import com.yami.trading.bean.item.domain.Item;
+import com.yami.trading.bean.model.MoneyLog;
+import com.yami.trading.bean.model.User;
+import com.yami.trading.bean.model.Wallet;
+import com.yami.trading.bean.model.WalletExtend;
+import com.yami.trading.common.constants.Constants;
+import com.yami.trading.common.domain.Result;
+import com.yami.trading.common.exception.YamiShopBindException;
+import com.yami.trading.common.util.Arith;
+import com.yami.trading.common.util.RandomUtil;
+import com.yami.trading.dao.ats.StockAtsMapper;
+import com.yami.trading.service.MoneyLogService;
+import com.yami.trading.service.WalletService;
+import com.yami.trading.service.ats.StockAtsService;
+import com.yami.trading.service.exchange.ExchangeApplyOrderService;
+import com.yami.trading.service.item.ItemService;
+import com.yami.trading.service.user.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+
+@Service
+@Slf4j
+public class StockAtsServiceImpl extends ServiceImpl<StockAtsMapper, StockAts> implements StockAtsService {
+
+ @Autowired
+ StockAtsMapper stockAtsMapper;
+ @Autowired
+ UserService userService;
+ @Autowired
+ WalletService walletService;
+ @Autowired
+ MoneyLogService moneyLogService;
+ @Autowired
+ ExchangeApplyOrderService exchangeApplyOrderService;
+ @Autowired
+ ItemService itemService;
+
+ @Override
+ public Result getList(int pageNum, int pageSize, String partyId) {
+ try {
+ QueryWrapper<StockAts> queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("user_id", partyId);
+
+ Page<StockAts> page = new Page<>(pageNum, pageSize);
+ page = stockAtsMapper.selectPage(page, queryWrapper);
+ return Result.succeed(page);
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ }
+ return Result.failed("获取失败");
+ }
+
+ @Transactional
+ @Override
+ public Result buyAts(double price, String partyId) {
+ try {
+ if (price <= 0) {
+ throw new YamiShopBindException("请输入");
+ }
+ User party = userService.getById(partyId);
+ if (!party.isEnabled()) {
+ throw new YamiShopBindException("用户已禁用");
+ }
+
+ BigDecimal amt = BigDecimal.valueOf(price);
+
+ Wallet wallet = this.walletService.saveWalletByPartyId(partyId);
+ if (wallet.getMoney().compareTo(amt) < 0) {
+ return Result.failed("余额不足");
+ }
+
+
+ StockAts order = new StockAts();
+ order.setUserId(partyId);
+ order.setPrice(amt);
+ order.setAddTime(new Date());
+ order.setOrderNo(com.yami.trading.common.util.DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(8));
+ stockAtsMapper.insert(order);
+
+ return Result.succeed("提交成功");
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ return Result.failed("失败");
+ }
+ }
+
+ @Override
+ public Result<Page<StockAtsDto>> getListByAdmin(int pageNum, int pageSize, String keywords) {
+ try {
+ Page<StockAtsDto> page = new Page<>(pageNum, pageSize);
+ stockAtsMapper.getListByAdmin(page, keywords);
+ return Result.succeed(page);
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ }
+ return Result.failed("获取失败");
+ }
+
+ @Transactional
+ @Override
+ public Result atsCheck(String id, Integer checkType, String stockCode, double closePrice, double price) {
+ try {
+ StockAts order = stockAtsMapper.selectById(id);
+ if (order == null) {
+ throw new YamiShopBindException("订单不存在");
+ }
+ if (!order.getState().equals(ExchangeApplyOrderDz.STATE_SUBMITTED)) {
+ throw new YamiShopBindException("订单已审核");
+ }
+ //1.通过 2.拒绝
+ if (checkType == 2) {
+ order.setState(ExchangeApplyOrderDz.STATE_FAILED);
+ stockAtsMapper.updateById(order);
+ return Result.succeed("操作成功");
+ }
+ if (price <= 0 || closePrice <= 0) {
+ throw new YamiShopBindException("输入正确金额");
+ }
+
+
+ Wallet wallet = this.walletService.saveWalletByPartyId(order.getUserId());
+ BigDecimal buyAmt = BigDecimal.valueOf(price);
+ BigDecimal closeAmt = BigDecimal.valueOf(closePrice);
+ BigDecimal orderFree = BigDecimal.ZERO;;
+ if (buyAmt.doubleValue() < 300) {
+ orderFree = BigDecimal.ONE;
+ }
+ BigDecimal orderAmt = buyAmt.add(orderFree);
+ if (wallet.getMoney().compareTo(orderAmt) < 0) {
+ throw new YamiShopBindException("订单失败,用户资金不足");
+ }
+
+ Item item = itemService.findBySymbol(stockCode);
+ if (item == null) {
+ throw new YamiShopBindException("股票代码不存在");
+ }
+ if (!item.getType().equalsIgnoreCase(Item.US_STOCKS)) {
+ throw new YamiShopBindException("请输入美股代码");
+ }
+
+ BigDecimal amountBefore = wallet.getMoney();
+
+ order.setStockCode(item.getSymbol());
+ order.setStockName(item.getName());
+ order.setStockSpell(item.getSymbolData());
+ order.setPrice(buyAmt);
+ order.setFee(orderFree.doubleValue());
+ order.setState(ExchangeApplyOrderDz.STATE_POSITION);
+ order.setBuyTime(new Date());
+ order.setClosePrice(closeAmt);
+ stockAtsMapper.updateById(order);
+ walletService.update(wallet.getUserId(), Arith.sub(0, orderAmt.doubleValue()));
+
+ //TODO 转持仓
+ intoPosition(order);
+
+ MoneyLog log = new MoneyLog();
+ log.setCategory(Constants.MONEYLOG_CATEGORY_EXCHANGE);
+ String name = Constants.MONEYLOG_MAP.get(Item.US_STOCKS);
+ String type2 = Constants.MONEYLOG_MAP_TYPE.get(Item.US_STOCKS);
+ log.setAmountBefore(amountBefore);
+ log.setAmount(orderAmt.negate());
+ log.setAmountAfter(amountBefore.subtract(orderAmt));
+ log.setLog(name + type2 + "ATS买入成交" + ",订单号[" + order.getOrderNo() + "]");
+ log.setUserId(order.getUserId());
+ log.setWalletType(Constants.WALLET);
+ log.setSymbol(order.getStockCode());
+ log.setContentType(type2 + Constants.MONEYLOG_CONTENT_OPEN);
+ log.setCreateTime(new Date());
+ log.setUpdateTime(new Date());
+ moneyLogService.save(log);
+ return Result.succeed("审核成功,订单已转持仓");
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ return Result.failed("操作失败:" + e.getMessage());
+ }
+ }
+
+ public void intoPosition(StockAts order) {
+ //到账币种数量
+ BigDecimal buyNum = order.getPrice().divide(order.getClosePrice(), 5, RoundingMode.HALF_UP);
+
+ ExchangeApplyOrder exOrder = new ExchangeApplyOrder();
+ exOrder.setPartyId(order.getUserId());
+ exOrder.setSymbol(order.getStockCode());
+ exOrder.setOffset(ExchangeApplyOrder.OFFSET_OPEN);
+ exOrder.setSymbolValue(buyNum.doubleValue());
+ exOrder.setVolume(order.getPrice().doubleValue());
+ exOrder.setPrice(order.getClosePrice().doubleValue());
+ exOrder.setOrderPriceType(ContractApplyOrder.ORDER_PRICE_TYPE_OPPONENT);
+ exOrder.setState(ExchangeApplyOrder.STATE_CREATED);
+ exOrder.setClosePrice(order.getClosePrice().doubleValue());
+ exOrder.setOrderNo(com.yami.trading.common.util.DateUtil.getToday("yyMMddHHmmss") + RandomUtil.getRandomNum(8));
+ exOrder.setFee(order.getFee());
+ exOrder.setCreateTime(new Date());
+
+ double realValue = exOrder.getSymbolValue();
+ //入账
+ WalletExtend walletExtend = walletService.saveExtendByPara(exOrder.getPartyId(), exOrder.getSymbol());
+ double amountBeforeExtend = walletExtend.getAmount();
+ this.walletService.updateExtend(walletExtend.getPartyId(), walletExtend.getWallettype(), realValue);
+
+ MoneyLog log = new MoneyLog();
+ log.setCategory(Constants.MONEYLOG_CATEGORY_EXCHANGE);
+ String name = Constants.MONEYLOG_MAP.get(Item.US_STOCKS);
+ String type2 = Constants.MONEYLOG_MAP_TYPE.get(Item.US_STOCKS);
+ log.setAmountBefore(new BigDecimal(amountBeforeExtend));
+ log.setAmount(BigDecimal.valueOf(realValue));
+ log.setAmountAfter(BigDecimal.valueOf(amountBeforeExtend + realValue));
+ log.setLog(name + type2 + "ATS买入委托单成交,订单号[" + exOrder.getOrderNo() + "]");
+ log.setUserId(exOrder.getPartyId());
+ log.setSymbol(exOrder.getSymbol());
+ log.setWalletType(exOrder.getSymbol());
+ log.setContentType(type2 + Constants.MONEYLOG_CONTENT_OPEN);
+ // 记录账变日志
+ moneyLogService.save(log);
+
+ exchangeApplyOrderService.save(exOrder);
+ }
+
+}
diff --git a/trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml b/trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml
new file mode 100644
index 0000000..b8ad09a
--- /dev/null
+++ b/trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.yami.trading.dao.ats.StockAtsMapper">
+
+
+ <select id="getListByAdmin" resultType="com.yami.trading.bean.ats.dto.StockAtsDto" parameterType="map">
+ SELECT
+ a.*,u.user_code,u.user_name
+ FROM t_stock_ats a
+ LEFT JOIN tz_user u ON u.user_id = a.user_id
+ WHERE 1=1
+ <if test="keyWords != null and keyWords != '' ">
+ AND (
+ u.user_name LIKE CONCAT('%', #{keyWords}, '%')
+ OR u.user_code LIKE CONCAT('%', #{keyWords}, '%')
+ )
+ </if>
+ ORDER BY a.add_time DESC
+ </select>
+
+
+</mapper>
--
Gitblit v1.9.3