1
zj
2025-04-23 decfc2ff7244cec365d3f32a5090edf5faeae861
1
15 files modified
1 files added
690 ■■■■■ changed files
src/main/java/com/nq/controller/StockApiController.java 15 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/StockIndexApiController.java 64 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/backend/AdminController.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/protol/UserController.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserAssetsServices.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/IUserPositionService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/StockIndexServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/StockServiceImpl.java 201 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserAssetsServices.java 145 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java 85 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/service/impl/UserServiceImpl.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/redis/RedisKeyConstant.java 9 ●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/task/stock/StockTask.java 80 ●●●●● patch | view | raw | blame | history
src/main/java/com/nq/utils/translate/GoogleTranslateUtil.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/vo/stockindex/StockIndexVO.java 1 ●●●● patch | view | raw | blame | history
src/main/resources/application.properties 2 ●●● patch | view | raw | blame | history
src/main/java/com/nq/controller/StockApiController.java
@@ -4,6 +4,9 @@
import com.nq.pojo.SiteSetting;
import com.nq.service.IStockService;
import com.nq.service.StockDzService;
import com.nq.utils.redis.RedisKeyConstant;
import com.nq.utils.redis.RedisShardedPoolUtils;
import com.nq.utils.translate.GoogleTranslateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -14,6 +17,7 @@
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@Controller
@RequestMapping({"/api/stock/"})
@@ -52,6 +56,17 @@
        return this.iStockService.getStockByType(pageNum, pageSize, orderBy,keyWords , stockType, request);
    }
    @RequestMapping("getGoldCrudeOil.do")
    @ResponseBody
    public ServerResponse getGoldCrudeOil(HttpServletRequest request)   {
        String gold = RedisShardedPoolUtils.get(RedisKeyConstant.gold);
        String crudeOil = RedisShardedPoolUtils.get(RedisKeyConstant.crude_oil);
        HashMap<String,String> map = new HashMap<>();
        map.put(new GoogleTranslateUtil().translate("GOLD",request.getHeader("lang")),gold);
        map.put(new GoogleTranslateUtil().translate("CRUDE OIL",request.getHeader("lang")),crudeOil);
        return ServerResponse.createBySuccess(map);
    }
    @RequestMapping("getOptionStock.do")
src/main/java/com/nq/controller/StockIndexApiController.java
New file
@@ -0,0 +1,64 @@
package com.nq.controller;
import com.nq.common.ServerResponse;
import com.nq.service.IStockIndexService;
import com.nq.vo.stock.MarketVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Controller
@RequestMapping({"/api/index/"})
public class StockIndexApiController {
    private static final Logger log = LoggerFactory.getLogger(StockIndexApiController.class);
    @Autowired
    IStockIndexService iStockIndexService;
    @RequestMapping({"queryHomeIndex.do"})
    @ResponseBody
    public ServerResponse queryHomeIndex() {
        return this.iStockIndexService.queryHomeIndex();
    }
    //查询指数信息
    @RequestMapping({"queryListIndex.do"})
    @ResponseBody
    public ServerResponse queryListIndex(HttpServletRequest request) {
        return this.iStockIndexService.queryListIndex(request);
    }
    //查询固定指数信息
    @RequestMapping({"queryListIndexByCode.do"})
    @ResponseBody
    public ServerResponse queryListIndexByCode(@RequestParam List<String> code) {
        return this.iStockIndexService.queryListIndexByCode(code);
    }
    @RequestMapping({"queryTransIndex.do"})
    @ResponseBody
    public ServerResponse queryTransIndex(@RequestParam("indexId") Integer indexId) {
        return this.iStockIndexService.queryTransIndex(indexId);
    }
    @RequestMapping({"querySingleIndex.do"})
    @ResponseBody
    public ServerResponse querySingleIndex(@RequestParam("indexCode") String indexCode) {
        MarketVO marketVO = this.iStockIndexService.querySingleIndex(indexCode);
        return ServerResponse.createBySuccess(marketVO);
    }
    //指数新闻
    @RequestMapping({"queryIndexNews.do"})
    @ResponseBody
    public ServerResponse queryIndexNews() {
        return this.iStockIndexService.queryIndexNews();
    }
}
src/main/java/com/nq/controller/backend/AdminController.java
@@ -384,4 +384,14 @@
        return  iUserAssetsServices.updateUserAssets(id,amt,type);
    }
    @RequestMapping("transfer.do")
    @ResponseBody
    public  ServerResponse transfer(
            @RequestParam("userId") Integer userId,
            @RequestParam("disbursementAccount") String disbursementAccount,
            @RequestParam("depositAccount") String depositAccount,
            @RequestParam("amt") String amt, HttpServletRequest request
    ){
        return  iUserAssetsServices.transfer(userId,disbursementAccount,depositAccount,amt,request);
    }
}
src/main/java/com/nq/controller/protol/UserController.java
@@ -130,6 +130,32 @@
            buyOrderCreated.set(false);
        }
    }
    //用户下单买入股票
    @RequestMapping({"goldCrudeOilbuy.do"})
    @ResponseBody
    public ServerResponse goldCrudeOilbuy(@RequestParam("name") String name,
                              @RequestParam("buyNum") Integer buyNum,
                              @RequestParam("buyType") Integer buyType,
                              @RequestParam("lever") Integer lever,
                              @RequestParam(value = "profitTarget",required = false)
                              BigDecimal profitTarget,@RequestParam(value = "stopLoss",required = false) BigDecimal stopLoss, HttpServletRequest request) {
        buyLock.lock();
        try {
            if (buyOrderCreated.get()) {
                return ServerResponse.createByErrorMsg("当前下单人数过多,请稍后重试", request);
            }
            buyOrderCreated.set(true);
            return this.iUserPositionService.goldCrudeOilbuy(name, buyNum,lever,profitTarget,stopLoss, request);
        } catch (Exception e) {
            e.printStackTrace();
            return ServerResponse.createByErrorMsg("订单异常,请稍后重试", request);
        }  finally{
            buyLock.unlock();
            buyOrderCreated.set(false);
        }
    }
    //修改涨跌板
    @RequestMapping({"updateProfitTarget.do"})
    @ResponseBody
src/main/java/com/nq/service/IUserAssetsServices.java
@@ -1,14 +1,15 @@
package com.nq.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nq.common.ServerResponse;
import com.nq.enums.EUserAssets;
import com.nq.pojo.Bank;
import com.nq.pojo.UserAssets;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.List;
public interface IUserAssetsServices {
public interface IUserAssetsServices extends IService<UserAssets> {
@@ -48,4 +49,5 @@
    Boolean availablebalanceChange(String accetType, Integer userId, EUserAssets eUserAssets, BigDecimal amount, String desc, String descType);
    ServerResponse transfer(Integer userId, String disbursementAccount, String depositAccount, String amt, HttpServletRequest request);
}
src/main/java/com/nq/service/IUserPositionService.java
@@ -85,4 +85,6 @@
  ServerResponse buyStockDzList(HttpServletRequest request);
  void stockConstraint(List<UserPosition> userPositions);
  ServerResponse goldCrudeOilbuy(String name, Integer buyNum, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest requestrequest);
}
src/main/java/com/nq/service/impl/StockIndexServiceImpl.java
@@ -81,11 +81,13 @@
        MarketVO marketVO = querySingleIndex(stockIndex.getIndexGid());
        if(null != marketVO){
            stockIndexVO.setCurrentPoint(marketVO.getNowPrice());
            stockIndexVO.setFloatPoint(marketVO.getIncrease());
            stockIndexVO.setFloatRate(marketVO.getIncreaseRate());
            stockIndexVO.setType(marketVO.getType());
        }
        stockIndexVO.setCurrentPoint(marketVO.getNowPrice());
        stockIndexVO.setFloatPoint(marketVO.getIncrease());
        stockIndexVO.setFloatRate(marketVO.getIncreaseRate());
        stockIndexVO.setType(marketVO.getType());
        return stockIndexVO;
    }
@@ -195,14 +197,21 @@
    public MarketVO querySingleIndex(String indexCode) {
        MarketVO marketVO = null;
        if (indexCode.contains("hk")||indexCode.contains("us")){
            String index = RedisShardedPoolUtils.get(indexCode, 3);
            JSONObject jsonObject = JSONObject.parseObject(index);
            marketVO = new MarketVO();
            marketVO.setName(jsonObject.getString("f14"));
            marketVO.setNowPrice(jsonObject.getString("f2"));
            marketVO.setIncrease(jsonObject.getString("f4"));
            marketVO.setIncreaseRate(jsonObject.getString("f3"));
            marketVO.setType(jsonObject.getString("f13"));
//            String index = RedisShardedPoolUtils.get(indexCode, 3);
//            JSONObject jsonObject = JSONObject.parseObject(index);
//            marketVO = new MarketVO();
//            if(jsonObject.containsKey("f14") &&
//                    jsonObject.containsKey("f2") &&
//                    jsonObject.containsKey("f4") &&
//                    jsonObject.containsKey("f3") &&
//                    jsonObject.containsKey("f13")){
//                marketVO.setName(jsonObject.getString("f14"));
//                marketVO.setNowPrice(jsonObject.getString("f2"));
//                marketVO.setIncrease(jsonObject.getString("f4"));
//                marketVO.setIncreaseRate(jsonObject.getString("f3"));
//                marketVO.setType(jsonObject.getString("f13"));
//            }
        }else {
            String market_url = PropertiesUtil.getProperty("sina.single.market.url");
src/main/java/com/nq/service/impl/StockServiceImpl.java
@@ -21,6 +21,7 @@
import com.nq.service.*;
import com.nq.utils.http.HttpClientRequest;
import com.nq.utils.PropertiesUtil;
import com.nq.utils.redis.RedisKeyConstant;
import com.nq.utils.redis.RedisKeyUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
import com.nq.utils.stock.pinyin.GetPyByChinese;
@@ -38,6 +39,11 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Resource;
@@ -53,6 +59,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import static com.nq.utils.timeutil.DateTimeUtil.getWeekDay;
@@ -190,33 +197,52 @@
    public ServerResponse getSingleStock(String code, HttpServletRequest request) {
        if (StringUtils.isBlank(code))
            return ServerResponse.createByErrorMsg("");
        Stock stock = stockMapper.findStockByCode(code);
        DataStockBean cacheBaseStock = RedisKeyUtil.getCacheBaseStock(stock);
        Integer depositAmt = 0;
        String introduction = null;
        StockVO   stockVO = StockApi.assembleInStockVO(stock);
        stockVO.setDepositAmt(depositAmt);
        stockVO.setNowPrice(iPriceServices.getNowPrice(stock.getStockCode()).toString());
        stockVO.setType(stock.getStockType());
        stockVO.setId(stock.getId().intValue());
        stockVO.setCode(stock.getStockCode());
        stockVO.setName(stock.getStockName());
        stockVO.setSpell(stock.getStockSpell());
        stockVO.setGid(stock.getStockGid().toUpperCase());
        Map<String, Object> newStock = iPriceServices.getNewStock(stock.getStockCode());
        stockVO.setHcrate(newStock.get("ChgPct").toString().replaceAll("%", ""));
        stockVO.setToday_max(newStock.get("High").toString());
        stockVO.setToday_min(newStock.get("Low").toString());
        if(null != cacheBaseStock){
            stockVO.setOpen_px(cacheBaseStock.getOpen());
            stockVO.setPreclose_px(cacheBaseStock.getPrevClose());
        if(code.equals("GOLD")){
            String price =  RedisShardedPoolUtils.get(RedisKeyConstant.gold);
            StockVO stockVO = new StockVO();
            stockVO.setNowPrice(price);
            stockVO.setName(code);
            Map map = Maps.newHashMap();
            map.put("stock", stockVO);
            return ServerResponse.createBySuccess(map);
        }else if(code.equals("CRUDE OIL")){
            String price = RedisShardedPoolUtils.get(RedisKeyConstant.crude_oil);
            StockVO stockVO = new StockVO();
            stockVO.setNowPrice(price);
            stockVO.setName(code);
            Map map = Maps.newHashMap();
            map.put("stock", stockVO);
            return ServerResponse.createBySuccess(map);
        }else{
            if (StringUtils.isBlank(code))
                return ServerResponse.createByErrorMsg("");
            Stock stock = stockMapper.findStockByCode(code);
            DataStockBean cacheBaseStock = RedisKeyUtil.getCacheBaseStock(stock);
            Integer depositAmt = 0;
            String introduction = null;
            StockVO   stockVO = StockApi.assembleInStockVO(stock);
            stockVO.setDepositAmt(depositAmt);
            stockVO.setNowPrice(iPriceServices.getNowPrice(stock.getStockCode()).toString());
            stockVO.setType(stock.getStockType());
            stockVO.setId(stock.getId().intValue());
            stockVO.setCode(stock.getStockCode());
            stockVO.setName(stock.getStockName());
            stockVO.setSpell(stock.getStockSpell());
            stockVO.setGid(stock.getStockGid().toUpperCase());
            Map<String, Object> newStock = iPriceServices.getNewStock(stock.getStockCode());
            stockVO.setHcrate(newStock.get("ChgPct").toString().replaceAll("%", ""));
            stockVO.setToday_max(newStock.get("High").toString());
            stockVO.setToday_min(newStock.get("Low").toString());
            if(null != cacheBaseStock){
                stockVO.setOpen_px(cacheBaseStock.getOpen());
                stockVO.setPreclose_px(cacheBaseStock.getPrevClose());
            }
            Map map = Maps.newHashMap();
            map.put("introduction", introduction);
            map.put("stock", stockVO);
            return ServerResponse.createBySuccess(map);
        }
        Map map = Maps.newHashMap();
        map.put("introduction", introduction);
        map.put("stock", stockVO);
        return ServerResponse.createBySuccess(map);
    }
    public Map getSingleStock(String code) {
@@ -432,25 +458,118 @@
    }
    //黄金
    private static final String gold_API_URL = "http://139.196.211.109/ldMetal_k.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=AUUSDO&num=-100&period=";
    //原油
    private static final String crude_oil_API_URL = "http://47.112.169.122/fOption_k.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=@CL0W&num=-100&period=";
        /*股票日线-K线*/
    @Override
    public Object getKData(String pid, String interval, String stockType) {
        EStockType eStockType = EStockType.getEStockTypeByCode(stockType);
        Object object = HttpUtil.get(eStockType.stockUrl + "kline?pid=" + pid + "&interval=" + interval + "&key=" + eStockType.stockKey);
        Gson gson = new Gson();
        List<kData> dataList = gson.fromJson(object.toString(), new TypeToken<List<kData>>(){}.getType());
        if(stockType.equals("GOLD")){
            // 使用RestTemplate发起HTTP请求
            String response = RedisShardedPoolUtils.get("k_gold_"+interval.toLowerCase());
            return parseData(interval,response, stockType);
        }else if(stockType.equals("CRUDE OIL")){
            // 使用RestTemplate发起HTTP请求
            RestTemplate restTemplate = new RestTemplate();
            String response = RedisShardedPoolUtils.get("k_crude_oil_"+interval.toLowerCase());
            return parseData(interval,response, stockType);
        }else{
            EStockType eStockType = EStockType.getEStockTypeByCode(stockType);
            Object object = HttpUtil.get(eStockType.stockUrl + "kline?pid=" + pid + "&interval=" + interval + "&key=" + eStockType.stockKey);
            Gson gson = new Gson();
            List<kData> dataList = gson.fromJson(object.toString(), new TypeToken<List<kData>>(){}.getType());
        Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, pid).eq(Stock::getStockType, EStockType.ST.getCode()));
        BigDecimal nowPrice = iPriceServices.getNowPrice(stock.getStockCode());
        Map singleStock = getSingleStock(stock.getStockCode());
        StockVO stockVO = (StockVO)singleStock.get("stock");
        // 修改 List 中的最后一条数据
        kData lastData = dataList.get(dataList.size() - 1);
        lastData.setC(nowPrice.toString());
        lastData.setO(stockVO.getOpen_px());
        lastData.setH(stockVO.getToday_max());
        lastData.setL(stockVO.getToday_min());
        return gson.toJson(dataList);
            Stock stock = stockMapper.selectOne(new LambdaQueryWrapper<Stock>().eq(Stock::getStockCode, pid).eq(Stock::getStockType, EStockType.ST.getCode()));
            BigDecimal nowPrice = iPriceServices.getNowPrice(stock.getStockCode());
            Map singleStock = getSingleStock(stock.getStockCode());
            StockVO stockVO = (StockVO)singleStock.get("stock");
            // 修改 List 中的最后一条数据
            kData lastData = dataList.get(dataList.size() - 1);
            lastData.setC(nowPrice.toString());
            lastData.setO(stockVO.getOpen_px());
            lastData.setH(stockVO.getToday_max());
            lastData.setL(stockVO.getToday_min());
            return gson.toJson(dataList);
        }
    }
    public List<kData> parseData(String interval,String data,String key) {
        String price = null;
        if(key.equals("GOLD")){
            price =  RedisShardedPoolUtils.get(RedisKeyConstant.gold);
        }else if(key.equals("CRUDE OIL")){
            price = RedisShardedPoolUtils.get(RedisKeyConstant.crude_oil);
        }
        List<kData> kDataList = new ArrayList<>();
        // 将数据按行分割
        String[] lines = data.split("\n");
        // 跳过第一行(列名)
        for (int i = 1; i < lines.length; i++) {
            String line = lines[i].trim();
            // 确保行不为空
            if (!line.isEmpty()) {
                String[] fields = line.split(",");
                // 确保每行有至少7个字段:日期、开盘价、最高价、最低价、收盘价、成交量、持仓量
                if ((key.equals("CRUDE OIL") && fields.length == 7) || (key.equals("GOLD") && fields.length == 5)) {
                    kData kData = new kData();
                    kData.setT(convDate(fields[0],interval));
                    kData.setC(price);
                    kData.setO(fields[1]);
                    kData.setH(fields[2]);
                    kData.setL(fields[3]);
                    if(key.equals("CRUDE OIL")){
                        kData.setV(fields[4]);
                        kData.setVo(fields[5]);
                    }else{
                        kData.setV("0");
                        kData.setVo("0");
                    }
                    // 将每一条 KData 对象添加到列表中
                    kDataList.add(kData);
                }
            }
        }
        return kDataList;
    }
    // 常量,定义日期格式
    private static final String DATE_PATTERN = "yyyyMMdd";
    private static final String DATE_TIME_PATTERN = "yyyyMMdd HH:mm";
    public long convDate(String dateStr, String interval) {
        // 输入校验,确保 dateStr 和 interval 不为 null 或空
        if (dateStr == null || dateStr.trim().isEmpty() || interval == null || interval.trim().isEmpty()) {
            throw new IllegalArgumentException("日期字符串或时间间隔不能为空。");
        }
        // 根据 interval 的值,选择日期格式
        String pattern = (interval.equalsIgnoreCase("D") || interval.equalsIgnoreCase("W") || interval.equalsIgnoreCase("M"))
                ? DATE_PATTERN : DATE_TIME_PATTERN;
        // 创建 DateTimeFormatter,使用合适的格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        try {
            // 根据是否包含时间部分,选择 LocalDate 或 LocalDateTime 进行解析
            if (pattern.equals(DATE_PATTERN)) {
                LocalDate localDate = LocalDate.parse(dateStr, formatter);
                Instant timestamp = localDate.atStartOfDay(ZoneOffset.UTC).toInstant();
                return timestamp.getEpochSecond();
            } else {
                LocalDateTime localDateTime = LocalDateTime.parse(dateStr, formatter);
                Instant timestamp = localDateTime.toInstant(ZoneOffset.UTC);
                return timestamp.getEpochSecond();
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("无效的日期格式: " + dateStr, e);
        }
    }
    @Override
src/main/java/com/nq/service/impl/UserAssetsServices.java
@@ -1,8 +1,9 @@
package com.nq.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nq.common.ServerResponse;
import com.nq.dao.*;
import com.nq.enums.EStockType;
@@ -13,13 +14,13 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.validation.constraints.Email;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
/**
 * 用户资产
@@ -259,6 +260,95 @@
        return userAssetsMapper.updateById(userAssets)>1;
    }
    @Override
    @Transactional
    public ServerResponse transfer(Integer userId, String disbursementAccount, String depositAccount, String amt, HttpServletRequest request) {
        // 参数校验
        ServerResponse paramCheckResponse = checkParameters(userId, disbursementAccount, depositAccount, amt, request);
        if (paramCheckResponse != null) {
            return paramCheckResponse;
        }
        // 转账金额合法性检查
        BigDecimal transferAmount = validateTransferAmount(amt, request);
        if (transferAmount == null) {
            return ServerResponse.createByErrorMsg("无效的转账金额", request);
        }
        // 查询转出账户和转入账户的资产信息
        UserAssets disbursement = getUserAssets(userId, disbursementAccount);
        UserAssets deposit = getUserAssets(userId, depositAccount);
        // 账户检查
        if (disbursement == null) {
            return ServerResponse.createByErrorMsg("转出账户不存在", request);
        }
        if (deposit == null) {
            return ServerResponse.createByErrorMsg("转入账户不存在", request);
        }
        // 检查转出账户余额是否足够
        if (isBalanceSufficient(disbursement.getAvailableBalance(), transferAmount)) {
            return ServerResponse.createByErrorMsg("转出账户余额不足", request);
        }
        // 执行转账操作
        try {
            synchronized (userId){
                performTransfer(disbursement, deposit, transferAmount);
            }
            return ServerResponse.createBySuccess("划转成功", request);
        } catch (Exception e) {
            log.error("划转失败: " + e.getMessage(), e);
            return ServerResponse.createByErrorMsg("划转失败", request);
        }
    }
    // 参数校验
    private ServerResponse checkParameters(Integer userId, String disbursementAccount, String depositAccount, String amt, HttpServletRequest request) {
        if (userId == null || disbursementAccount == null || depositAccount == null || amt == null) {
            return ServerResponse.createByErrorMsg("输入参数不能为空", request);
        }
        return null;
    }
    // 转账金额校验
    private BigDecimal validateTransferAmount(String amt, HttpServletRequest request) {
        try {
            BigDecimal transferAmount = BigDecimal.valueOf(Double.valueOf(amt));
            if (transferAmount.compareTo(BigDecimal.ZERO) <= 0) {
                ServerResponse.createByErrorMsg("转账金额必须大于零", request);
                return null;
            }
            return transferAmount;
        } catch (NumberFormatException e) {
            return null;
        }
    }
    // 获取账户资产信息
    private UserAssets getUserAssets(Integer userId, String accountType) {
        return userAssetsMapper.selectOne(new LambdaQueryWrapper<>(UserAssets.class)
                .eq(UserAssets::getAccectType, accountType)
                .eq(UserAssets::getUserId, userId));
    }
    // 判断余额是否足够
    private boolean isBalanceSufficient(BigDecimal balance, BigDecimal transferAmount) {
        return balance.compareTo(transferAmount) < 0;
    }
    // 执行转账操作
    private void performTransfer(UserAssets disbursement, UserAssets deposit, BigDecimal transferAmount) {
        // 更新转出账户余额
        disbursement.setAvailableBalance(disbursement.getAvailableBalance().subtract(transferAmount));
        userAssetsMapper.updateById(disbursement);
        // 更新转入账户余额
        deposit.setAvailableBalance(deposit.getAvailableBalance().add(transferAmount));
        userAssetsMapper.updateById(deposit);
    }
    //只要涉及到cumulativeProfitAndLoss变动重新设置状态
    private static void extracted(UserAssets userAssets) {
        if(userAssets.getCumulativeProfitAndLoss().compareTo(BigDecimal.ZERO) >= 0){
@@ -288,4 +378,49 @@
        }
        extracted(userAssets);
    }
    @Override
    public boolean saveBatch(Collection<UserAssets> entityList, int batchSize) {
        return false;
    }
    @Override
    public boolean saveOrUpdateBatch(Collection<UserAssets> entityList, int batchSize) {
        return false;
    }
    @Override
    public boolean updateBatchById(Collection<UserAssets> entityList, int batchSize) {
        return false;
    }
    @Override
    public boolean saveOrUpdate(UserAssets entity) {
        return false;
    }
    @Override
    public UserAssets getOne(Wrapper<UserAssets> queryWrapper, boolean throwEx) {
        return null;
    }
    @Override
    public Map<String, Object> getMap(Wrapper<UserAssets> queryWrapper) {
        return null;
    }
    @Override
    public <V> V getObj(Wrapper<UserAssets> queryWrapper, Function<? super Object, V> mapper) {
        return null;
    }
    @Override
    public BaseMapper<UserAssets> getBaseMapper() {
        return null;
    }
    @Override
    public Class<UserAssets> getEntityClass() {
        return null;
    }
}
src/main/java/com/nq/service/impl/UserPositionServiceImpl.java
@@ -15,7 +15,9 @@
import com.google.common.collect.Lists;
import com.nq.common.ServerResponse;
import com.nq.utils.*;
import com.nq.utils.redis.RedisKeyConstant;
import com.nq.utils.redis.RedisKeyUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
import com.nq.utils.stock.BuyAndSellUtils;
import com.nq.utils.stock.GeneratePosition;
import com.nq.utils.stock.GetStayDays;
@@ -240,6 +242,89 @@
        }
    }
    @Transactional
    public ServerResponse goldCrudeOilbuy(String name, Integer buyNum, Integer lever, BigDecimal profitTarget, BigDecimal stopTarget, HttpServletRequest request) {
        SiteProduct siteProduct = iSiteProductService.getProductSetting();
        User user = this.iUserService.getCurrentRefreshUser(request);
        synchronized (user.getId()){
            if (siteProduct.getRealNameDisplay() && user.getIsActive() != 2) {
                return ServerResponse.createByErrorMsg("订单失败,请先实名认证", request);
            }
            // 手续费率
            BigDecimal siteSettingBuyFee = new BigDecimal(iStockConfigServices.queryByKey(EConfigKey.BUY_HANDLING_CHARGE.getCode()).getCValue()) ;
            if (siteProduct.getRealNameDisplay() && user.getIsLock().intValue() == 1) {
                return ServerResponse.createByErrorMsg("订单失败,帐户已被锁定", request);
            }
            String s = RedisShardedPoolUtils.get(name);
            BigDecimal price = new BigDecimal(s);
            if (price == null) {
                return ServerResponse.createByErrorMsg("下单失败,请稍候再试!", request);
            }
            UserAssets userAssets = iUserAssetsServices.assetsByTypeAndUserId("USDT", user.getId());
            if(userAssets.getAmountToBeCovered().compareTo(BigDecimal.ZERO) > 0){
                return ServerResponse.createByErrorMsg("请先缴清待补资金", request);
            }
            if (price.compareTo(new BigDecimal("0")) == 0) {
                return ServerResponse.createByErrorMsg("报价0,请稍后再试", request);
            }
            BigDecimal buyAmt = price.multiply(new BigDecimal(buyNum)).divide(new BigDecimal(lever));
            BigDecimal orderFree = siteSettingBuyFee.multiply(buyAmt);
            BigDecimal   fundratio = new BigDecimal(user.getFundRatio()).divide(new BigDecimal(100));
            BigDecimal availableBalance =  fundratio.multiply(userAssets.getAvailableBalance());
            if (availableBalance.compareTo(buyAmt.add(orderFree)) < 0) {
                return ServerResponse.createByErrorMsg("订单失败,配资不足", request);
            }
            UserPosition userPosition = new UserPosition();
            if (profitTarget != null && profitTarget.compareTo(new BigDecimal("0")) > 0) {
                userPosition.setProfitTargetPrice(profitTarget);
            }
            if (stopTarget != null && stopTarget.compareTo(new BigDecimal("0")) > 0) {
                userPosition.setStopTargetPrice(stopTarget);
            }
            userPosition.setPositionType(user.getAccountType());
            userPosition.setPositionSn(KeyUtils.getUniqueKey());
            userPosition.setUserId(user.getId());
            userPosition.setNickName(user.getRealName());
            userPosition.setAgentId(user.getAgentId());
            userPosition.setStockCode(name);
            userPosition.setStockName(name);
            userPosition.setStockGid(name);
            userPosition.setStockSpell(name);
            userPosition.setBuyOrderId(GeneratePosition.getPositionId());
            userPosition.setBuyOrderTime(new Date());
            userPosition.setBuyOrderPrice(price);
            userPosition.setOrderDirection("买涨");
            userPosition.setOrderNum(buyNum);
            userPosition.setIsLock(Integer.valueOf(0));
            userPosition.setOrderLever(lever);
            userPosition.setOrderTotalPrice(buyAmt);
            // 手续费
            userPosition.setOrderFee(orderFree);
            userPosition.setOrderSpread(BigDecimal.ZERO);
            userPosition.setSpreadRatePrice(BigDecimal.ZERO);
            BigDecimal profit_and_lose = new BigDecimal("0");
            userPosition.setProfitAndLose(profit_and_lose);
            userPosition.setAllProfitAndLose(profit_and_lose.add(orderFree));
            userPosition.setOrderStayDays(Integer.valueOf(0));
            userPosition.setOrderStayFee(BigDecimal.ZERO);
            userPositionMapper.insert(userPosition);
            iUserAssetsServices.availablebalanceChange("USDT", user.getId(), EUserAssets.BUY, buyAmt.negate(), "", "");
            iUserAssetsServices.availablebalanceChange("USDT", user.getId(), EUserAssets.HANDLING_CHARGE, orderFree, "", "");
            return ServerResponse.createBySuccessMsg("下单成功", request);
        }
    }
    /**
     * 用户修改止盈止损
src/main/java/com/nq/service/impl/UserServiceImpl.java
@@ -209,6 +209,15 @@
                return ServerResponse.createByErrorMsg("登录失败。账户锁定",request);
            }
            userAssetsServices.assetsByTypeAndUserId(EStockType.ST.getCode(),user.getId());
            QueryWrapper<UserAssets>  usdtQueryWrapper = new QueryWrapper<>();
            usdtQueryWrapper.eq("accect_type","USDT");
            usdtQueryWrapper.eq("user_id",user.getId());
            UserAssets usdtUserAssets =  userAssetsServices.getOne(usdtQueryWrapper);
            if(usdtUserAssets == null){
                usdtUserAssets.setAccectType("USDT");
                usdtUserAssets.setUserId(user.getId());
                userAssetsServices.save(usdtUserAssets);
            }
            this.iSiteLoginLogService.saveLog(user, request);
            return ServerResponse.createBySuccess(user);
        }
src/main/java/com/nq/utils/redis/RedisKeyConstant.java
@@ -32,6 +32,13 @@
    public static final String verification_code= "verification_code";
    /**
     * 黄金行情
     */
    public static final String gold= "gold";
    /**
     * 原油行情
     */
    public static final String crude_oil= "crude_oil";
}
src/main/java/com/nq/utils/task/stock/StockTask.java
@@ -3,6 +3,8 @@
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.nq.dao.StockMapper;
import com.nq.dao.UserPositionMapper;
@@ -15,12 +17,15 @@
import com.nq.service.IStockService;
import com.nq.service.IUserPositionService;
import com.nq.utils.http.HttpClientRequest;
import com.nq.utils.redis.RedisKeyConstant;
import com.nq.utils.redis.RedisKeyUtil;
import com.nq.utils.redis.RedisShardedPoolUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Date;
@@ -77,7 +82,82 @@
        }
    }
    //黄金
    private static final String gold_API_URL = "http://139.196.211.109/exchange_curr.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=AUUSDO";
    //原油
    private static final String crude_oil_API_URL = "http://47.112.169.122/fOption_curr.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=@CL0W";
    @Scheduled(cron = "0/10 * * * * ?")  // 每6秒执行一次
    public void gold() {
        try {
            // 使用RestTemplate发起HTTP请求
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(gold_API_URL, String.class);
            // 解析返回的CSV格式数据,去除可能存在的换行符
            if (response != null) {
                // 清除换行符并按逗号分割数据
                String[] parts = response.trim().split(",");
                String price = parts[2].trim();  // "3348.4"
                // 转换价格为Double类型
                // 保存价格到Redis
                RedisShardedPoolUtils.set(RedisKeyConstant.gold, String.valueOf(price));
                log.info("黄金定时任务------成功");
            } else {
                log.info("黄金定时任务------没有接收到数据");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("黄金定时任务------请求或数据解析失败:" + e.getMessage());
        }
    }
    @Scheduled(cron = "0/15 * * * * ?")  // 每6秒执行一次
    public void crudeOil() {
        try {
            // 使用RestTemplate发起HTTP请求
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(crude_oil_API_URL, String.class);
            // 解析返回的CSV格式数据,去除可能存在的换行符
            if (response != null) {
                // 清除换行符并按逗号分割数据
                String[] parts = response.trim().split(",");
                String price = parts[2].trim();  // "3348.4"
                // 转换价格为Double类型
                // 保存价格到Redis
                RedisShardedPoolUtils.set(RedisKeyConstant.crude_oil, String.valueOf(price));
                log.info("原油定时任务------没有接收到数据");
            } else {
                log.info("原油定时任务------没有接收到数据");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("原油定时任务------请求或数据解析失败:" + e.getMessage());
        }
    }
    //黄金
    private static final String k_gold_API_URL = "http://139.196.211.109/ldMetal_k.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=AUUSDO&num=-100&period=";
    //原油
    private static final String k_crude_oil_API_URL = "http://47.112.169.122/fOption_k.action?username=Qq112233&password=3ce25a66d5b3a8cd661024fea6c79388&id=@CL0W&num=-100&period=";
    @Scheduled(cron = "0/6 * * * * ?")  // 每6秒执行一次
    public void getKDate() throws InterruptedException {
        RestTemplate restTemplate = new RestTemplate();
        String[] arr = {"d", "w", "m", "1", "5", "30"};
        for (String str : arr) {
            String g = restTemplate.getForObject(k_gold_API_URL+str, String.class);
            RedisShardedPoolUtils.set("k_gold_"+str, g);
            Thread.sleep(6000);
            String c = restTemplate.getForObject(k_crude_oil_API_URL+str, String.class);
            RedisShardedPoolUtils.set("k_crude_oil_"+str, c);
            Thread.sleep(6000);
        }
    }
    /**
     * 同步美国股票
     */
src/main/java/com/nq/utils/translate/GoogleTranslateUtil.java
@@ -15,7 +15,7 @@
    public String translate(String langFrom, String langTo,
                            String word) throws Exception {
        langTo = "ar-SA";
        //不为空则设置代理
//        String proxyHost = PropertiesUtil.getProperty("https.proxyHost");
//        String proxyPort = PropertiesUtil.getProperty("https.proxyPort");
src/main/java/com/nq/vo/stockindex/StockIndexVO.java
@@ -11,6 +11,7 @@
    private String indexGid;
    private Integer homeShow;
    private Integer listShow;
    private Integer random;
    private Integer transState;
    private Integer depositAmt;
src/main/resources/application.properties
@@ -62,7 +62,7 @@
ST_HTTP_API = http://api-sa.js-stock.top/
ST_WS_URL = ws://api-sa-ws.js-stock.top
ST_KEY = eiFMWvMcKgVlCYKD7S4y
ST_KEY = DP6jTI2ow7unfakP0fRM
#HK_HTTP_API = http://api-v1.js-stock.top/