zyy
2025-12-06 ab7e92d5154b5acf05699bd527c4d8b5fff10550
新增ATS
8 files added
492 ■■■■■ changed files
trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java 52 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java 71 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java 56 ●●●●● patch | view | raw | blame | history
trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java 17 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java 18 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java 18 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java 238 ●●●●● patch | view | raw | blame | history
trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml 22 ●●●●● patch | view | raw | blame | history
trading-order-admin/src/main/java/com/yami/trading/admin/controller/ats/AdminStockAtsController.java
New file
@@ -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);
    }
}
trading-order-admin/src/main/java/com/yami/trading/api/controller/ats/ApiStockAtsController.java
New file
@@ -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("订单异常,请稍后重试");
    }
}
trading-order-bean/src/main/java/com/yami/trading/bean/ats/StockAts.java
New file
@@ -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";
}
trading-order-bean/src/main/java/com/yami/trading/bean/ats/dto/StockAtsDto.java
New file
@@ -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;
}
trading-order-service/src/main/java/com/yami/trading/dao/ats/StockAtsMapper.java
New file
@@ -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);
}
trading-order-service/src/main/java/com/yami/trading/service/ats/StockAtsService.java
New file
@@ -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);
}
trading-order-service/src/main/java/com/yami/trading/service/ats/impl/StockAtsServiceImpl.java
New file
@@ -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);
    }
}
trading-order-service/src/main/resources/mapper/ats/StockAtsMapper.xml
New file
@@ -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>