Browse Source

管理端管理用户登录密码增添
历史信息导入相关字段填充实现

Zhangbw 2 months ago
parent
commit
63cb62831a

+ 10 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/controller/MiniappUserController.java

@@ -97,4 +97,14 @@ public class MiniappUserController extends BaseController {
     public R<Void> changeStatus(@RequestParam Long id, @RequestParam Integer status) {
         return toAjax(miniappUserService.updateStatus(id, status));
     }
+
+    /**
+     * 修改用户密码
+     */
+    @SaCheckPermission("miniapp:user:edit")
+    @Log(title = "小程序用户", businessType = BusinessType.UPDATE)
+    @PutMapping("/changePassword")
+    public R<Void> changePassword(@RequestParam Long id, @RequestParam String password) {
+        return toAjax(miniappUserService.updatePassword(id, password));
+    }
 }

+ 1 - 1
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/controller/UserSubscriptionController.java

@@ -64,7 +64,7 @@ public class UserSubscriptionController extends BaseController {
      */
     @SaCheckPermission("miniapp:subscription:edit")
     @Log(title = "订阅管理", businessType = BusinessType.UPDATE)
-    @PutMapping
+    @PutMapping("/batch")
     public R<Void> edit(@RequestBody BatchUpdateSubscriptionBo batchBo) {
         UserSubscriptionBo bo = new UserSubscriptionBo();
         bo.setExpireTime(batchBo.getExpireTime());

+ 5 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/domain/MiniappUser.java

@@ -36,6 +36,11 @@ public class MiniappUser {
      */
     private String phone;
 
+    /**
+     * 登录密码
+     */
+    private String password;
+
     /**
      * 用户昵称
      */

+ 5 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/domain/bo/MiniappUserBo.java

@@ -18,6 +18,11 @@ public class MiniappUserBo {
      */
     private String phone;
 
+    /**
+     * 登录密码
+     */
+    private String password;
+
     /**
      * 用户昵称
      */

+ 5 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/service/IMiniappUserService.java

@@ -51,4 +51,9 @@ public interface IMiniappUserService {
      * 修改用户状态
      */
     int updateStatus(Long id, Integer status);
+
+    /**
+     * 修改用户密码
+     */
+    int updatePassword(Long id, String password);
 }

+ 9 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/service/impl/MiniappUserServiceImpl.java

@@ -87,6 +87,15 @@ public class MiniappUserServiceImpl implements IMiniappUserService {
         return baseMapper.update(null, updateWrapper);
     }
 
+    @Override
+    public int updatePassword(Long id, String password) {
+        LambdaUpdateWrapper<MiniappUser> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(MiniappUser::getId, id)
+                .set(MiniappUser::getPassword, password)
+                .set(MiniappUser::getUpdateTime, LocalDateTime.now());
+        return baseMapper.update(null, updateWrapper);
+    }
+
     /**
      * 构建查询条件
      */

+ 17 - 0
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/controller/StockPoolController.java

@@ -19,6 +19,7 @@ import org.dromara.common.web.core.BaseController;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.LocalDate;
 import java.util.List;
 
 /**
@@ -91,4 +92,20 @@ public class StockPoolController extends BaseController {
     public R<Void> removeByCode(@RequestParam String stockCode, @RequestParam Integer poolType) {
         return toAjax(stockPoolService.removeByCodeAndType(stockCode, poolType));
     }
+
+    /**
+     * 补全历史数据
+     * 查询选择日期的前一天和当天的超短池数据,补全前一天数据的收盘价、隔日最高价和隔日涨幅
+     *
+     * @param importDate 导入日期(当天日期,格式:yyyy-MM-dd)
+     * @return 补全结果信息
+     */
+    @SaCheckPermission("stock:pool:import")
+    @Log(title = "股票池历史数据补全", businessType = BusinessType.UPDATE)
+    @PostMapping("/completeHistory")
+    public R<Void> completeHistory(@RequestParam("importDate") String importDate) throws Exception {
+        LocalDate parsedDate = LocalDate.parse(importDate);
+        String message = stockPoolService.completeHistoryData(parsedDate);
+        return R.ok(message);
+    }
 }

+ 16 - 1
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/domain/StockPool.java

@@ -49,7 +49,22 @@ public class StockPool {
     private LocalDate addDate;
 
     /**
-     * 状态:1-有效,0-已移除
+     * 当日收盘价
+     */
+    private BigDecimal closePrice;
+
+    /**
+     * 隔日最高价
+     */
+    private BigDecimal nextDayHigh;
+
+    /**
+     * 隔日涨幅(%)
+     */
+    private BigDecimal nextDayGain;
+
+    /**
+     * 状态:0-已移除,1-历史有效,2-当前有效
      */
     private Integer status;
 

+ 14 - 0
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/mapper/StockPoolMapper.java

@@ -2,10 +2,24 @@ package com.yingpai.stock.mapper;
 
 import com.yingpai.stock.domain.StockPool;
 import com.yingpai.stock.domain.vo.StockPoolVo;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
+import java.time.LocalDate;
+import java.util.List;
+
 /**
  * 股票池Mapper接口
  */
 public interface StockPoolMapper extends BaseMapperPlus<StockPool, StockPoolVo> {
+
+    /**
+     * 查询指定日期的超短池数据(包含历史有效和当前有效)
+     * @param addDate 加入日期
+     * @param poolType 池类型:1-超短池
+     * @return 股票池列表
+     */
+    @Select("SELECT * FROM stock_pool WHERE add_date = #{addDate} AND pool_type = #{poolType} AND status IN (1, 2)")
+    List<StockPool> selectByDateAndType(@Param("addDate") LocalDate addDate, @Param("poolType") Integer poolType);
 }

+ 10 - 0
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/service/IStockPoolService.java

@@ -5,6 +5,7 @@ import com.yingpai.stock.domain.vo.StockPoolVo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 
+import java.time.LocalDate;
 import java.util.Collection;
 import java.util.List;
 
@@ -52,4 +53,13 @@ public interface IStockPoolService {
      * 根据股票代码和池类型移除
      */
     Boolean removeByCodeAndType(String stockCode, Integer poolType);
+
+    /**
+     * 补全历史数据
+     * 查询选择日期的前一天和当天的超短池数据,补全前一天数据的收盘价、隔日最高价和隔日涨幅
+     *
+     * @param importDate 导入日期(当天日期)
+     * @return 补全结果信息
+     */
+    String completeHistoryData(LocalDate importDate) throws Exception;
 }

+ 11 - 6
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/service/impl/StockInfoServiceImpl.java

@@ -95,14 +95,19 @@ public class StockInfoServiceImpl implements IStockInfoService {
         List<String> stockCodes = stocks.stream()
             .map(StockInfo::getStockCode)
             .collect(Collectors.toList());
-        
-        // 级联删除股票池中的记录
+
+        // 将股票池中的记录状态改为1(历史有效)
         if (!stockCodes.isEmpty()) {
             LambdaQueryWrapper<StockPool> poolWrapper = Wrappers.lambdaQuery();
-            poolWrapper.in(StockPool::getStockCode, stockCodes);
-            stockPoolMapper.delete(poolWrapper);
+            poolWrapper.in(StockPool::getStockCode, stockCodes)
+                       .eq(StockPool::getStatus, 2);  // 只更新当前有效的记录
+            List<StockPool> pools = stockPoolMapper.selectList(poolWrapper);
+            for (StockPool pool : pools) {
+                pool.setStatus(1);  // 改为历史有效
+                stockPoolMapper.updateById(pool);
+            }
         }
-        
+
         // 删除股票信息
         return baseMapper.deleteByIds(ids) > 0;
     }
@@ -148,7 +153,7 @@ public class StockInfoServiceImpl implements IStockInfoService {
 
         LambdaQueryWrapper<StockPool> poolWrapper = Wrappers.lambdaQuery();
         poolWrapper.in(StockPool::getStockCode, codes)
-            .eq(StockPool::getStatus, 1);
+            .eq(StockPool::getStatus, 2);  // 只查询当前有效的记录
         List<StockPool> pools = stockPoolMapper.selectList(poolWrapper);
 
         Map<String, Set<Integer>> poolMap = new HashMap<>();

+ 122 - 57
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/service/impl/StockPoolHistoryServiceImpl.java

@@ -15,6 +15,7 @@ import com.yingpai.stock.domain.vo.StockPoolHistoryVo;
 import com.yingpai.stock.mapper.StockPoolHistoryMapper;
 import com.yingpai.stock.mapper.StockPoolMapper;
 import com.yingpai.stock.service.IStockPoolHistoryService;
+import com.yingpai.stock.service.IStockPoolService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.utils.StringUtils;
@@ -46,6 +47,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
 
     private final StockPoolHistoryMapper baseMapper;
     private final StockPoolMapper stockPoolMapper;
+    private final IStockPoolService stockPoolService;
 
     @Override
     public TableDataInfo<StockPoolHistoryVo> queryPageList(StockPoolHistoryBo bo, PageQuery pageQuery) {
@@ -101,18 +103,18 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         // 读取 Excel 数据
         String originalFilename = file.getOriginalFilename();
         log.info("导入文件名: {}, 文件大小: {} bytes", originalFilename, file.getSize());
-        
+
         // 判断文件类型
-        boolean isXls = originalFilename != null && originalFilename.toLowerCase().endsWith(".xls") 
+        boolean isXls = originalFilename != null && originalFilename.toLowerCase().endsWith(".xls")
             && !originalFilename.toLowerCase().endsWith(".xlsx");
         log.info("文件类型: {}", isXls ? "xls (Excel 97-2003)" : "xlsx (Excel 2007+)");
-        
+
         // 使用 Map 方式读取原始数据,避免类型转换问题
         List<Map<Integer, String>> rawDataList = new ArrayList<>();
-        
+
         // 使用 BufferedInputStream 支持 mark/reset
         InputStream inputStream = new BufferedInputStream(file.getInputStream());
-        
+
         try {
             if (isXls) {
                 // .xls 文件使用 GBK 编码读取(解决中文乱码问题)
@@ -151,19 +153,19 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         }
 
         log.info("读取到 {} 条原始数据", rawDataList.size());
-        
+
         // 输出前5条原始数据用于调试
         for (int i = 0; i < Math.min(5, rawDataList.size()); i++) {
             Map<Integer, String> row = rawDataList.get(i);
             log.info("=== 调试 - 第{}条原始数据(共{}列)===", i + 1, row.size());
             for (Map.Entry<Integer, String> entry : row.entrySet()) {
                 String value = entry.getValue();
-                log.info("  列{}: [{}] (长度:{})", entry.getKey(), 
+                log.info("  列{}: [{}] (长度:{})", entry.getKey(),
                     value != null && value.length() > 50 ? value.substring(0, 50) + "..." : value,
                     value != null ? value.length() : 0);
             }
         }
-        
+
         // 检查是否所有数据都在一列中(可能是制表符分隔的文本)
         if (!rawDataList.isEmpty()) {
             Map<Integer, String> firstRow = rawDataList.get(0);
@@ -186,17 +188,17 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                     log.info("制表符分割成功,每行 {} 列", splitDataList.get(0).size());
                     rawDataList.clear();
                     rawDataList.addAll(splitDataList);
-                    
+
                     // 再次输出调试信息
                     for (int i = 0; i < Math.min(3, rawDataList.size()); i++) {
                         Map<Integer, String> row = rawDataList.get(i);
-                        log.info("分割后第{}条: 列0=[{}], 列1=[{}], 列2=[{}]", 
+                        log.info("分割后第{}条: 列0=[{}], 列1=[{}], 列2=[{}]",
                             i + 1, row.get(0), row.get(1), row.get(2));
                     }
                 }
             }
         }
-        
+
         // 将原始数据转换为 StockPoolHistoryVo
         List<StockPoolHistoryVo> dataList = new ArrayList<>();
         for (Map<Integer, String> row : rawDataList) {
@@ -219,15 +221,23 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         }
 
         log.info("转换后数据 {} 条", dataList.size());
-        
+
         // 输出前5条转换后的数据用于调试
         for (int i = 0; i < Math.min(5, dataList.size()); i++) {
             StockPoolHistoryVo debugVo = dataList.get(i);
-            log.info("转换后第{}条: 代码=[{}], 名称=[{}], 涨幅=[{}], 收盘=[{}]", 
-                i + 1, debugVo.getStockCode(), debugVo.getStockName(), 
+            log.info("转换后第{}条: 代码=[{}], 名称=[{}], 涨幅=[{}], 收盘=[{}]",
+                i + 1, debugVo.getStockCode(), debugVo.getStockName(),
                 debugVo.getChangePercent(), debugVo.getClosePrice());
         }
 
+        // 如果是重复导入同一天的数据,先删除该日期的所有历史记录
+        LambdaQueryWrapper<StockPoolHistory> deleteLqw = Wrappers.lambdaQuery();
+        deleteLqw.eq(StockPoolHistory::getRecordDate, parsedRecordDate);
+        int deletedCount = baseMapper.delete(deleteLqw);
+        if (deletedCount > 0) {
+            log.info("删除同一天的旧历史记录: {} 条", deletedCount);
+        }
+
         int successNum = 0;
         int updateNum = 0;
         int failureNum = 0;
@@ -261,22 +271,22 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                 // 处理股票代码(去除可能的引号和公式格式)
                 String stockCode = vo.getStockCode();
                 String stockName = vo.getStockName();
-                
+
                 if (stockCode != null) {
                     // 去除前后空格
                     stockCode = stockCode.trim();
-                    
+
                     // 调试日志:记录前几条数据的原始值
                     if (i < 5) {
-                        log.info("第{}条数据 - 处理前代码: [{}], 长度: {}, 名称: [{}]", 
+                        log.info("第{}条数据 - 处理前代码: [{}], 长度: {}, 名称: [{}]",
                             i + 1, stockCode, stockCode.length(), stockName);
                     }
-                    
+
                     // 处理公式格式 ="000547"
                     if (stockCode.startsWith("=")) {
                         stockCode = stockCode.substring(1);
                     }
-                    
+
                     // 去除所有类型的引号(使用 Unicode 转义避免编码问题)
                     stockCode = stockCode.replace("\"", "");  // 双引号
                     stockCode = stockCode.replace("'", "");   // 单引号
@@ -284,28 +294,28 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                     stockCode = stockCode.replace("\u201D", "");  // 中文右双引号
                     stockCode = stockCode.replace("\u2018", "");  // 中文左单引号
                     stockCode = stockCode.replace("\u2019", "");  // 中文右单引号
-                    
+
                     // 去除所有空白字符(包括空格、制表符、换行符等)
                     stockCode = stockCode.replaceAll("\\s+", "");
-                    
+
                     // 只保留数字和字母(股票代码只应该包含这些字符)
                     stockCode = stockCode.replaceAll("[^0-9A-Za-z]", "");
-                    
+
                     // 确保长度不超过20个字符(数据库限制)
                     if (stockCode.length() > 20) {
-                        log.warn("第{}条数据 - 股票代码过长,截取前20位: [{}] -> [{}]", 
+                        log.warn("第{}条数据 - 股票代码过长,截取前20位: [{}] -> [{}]",
                             i + 1, stockCode, stockCode.substring(0, 20));
                         stockCode = stockCode.substring(0, 20);
                     }
-                    
+
                     // 调试日志:记录处理后的值
                     if (i < 5) {
                         log.info("第{}条数据 - 处理后代码: [{}], 长度: {}", i + 1, stockCode, stockCode.length());
                     }
-                    
+
                     vo.setStockCode(stockCode);
                 }
-                
+
                 // 处理股票名称(去除可能的特殊字符)
                 if (stockName != null) {
                     stockName = stockName.trim();
@@ -331,10 +341,10 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                     }
                     continue;
                 }
-                
+
                 // 验证股票代码格式(正常股票代码应该是6位数字)
                 String code = vo.getStockCode();
-                
+
                 // 如果代码超过6位,可能是多列数据被拼接了,尝试提取前6位
                 if (code.length() > 6) {
                     String originalCode = code;
@@ -343,7 +353,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                     log.info("第{}条数据 - 股票代码过长,尝试提取前6位: [{}] -> [{}]", i + 1, originalCode, code);
                     vo.setStockCode(code);
                 }
-                
+
                 // 验证股票代码是否为纯6位数字
                 if (!code.matches("^\\d{6}$")) {
                     log.warn("第{}条数据 - 股票代码不是6位数字,跳过: [{}]", i + 1, code);
@@ -353,7 +363,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                         .append("(股票代码应为6位数字)");
                     continue;
                 }
-                
+
                 // 检查必填字段 - 股票名称
                 if (StringUtils.isBlank(vo.getStockName())) {
                     failureNum++;
@@ -432,10 +442,17 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
             log.info("批量更新完成: {} 条", toUpdateList.size());
         }
 
-        // 如果导入日期是当天,将排名前十的股票加入强势池
-        if (parsedRecordDate.equals(LocalDate.now())) {
-            log.info("[强势池] 检测到导入日期为当天,开始将排名前十的股票加入强势池");
-            addTopTenToStrongPool(parsedRecordDate);
+        // 将排名前十的股票加入强势池
+        log.info("[强势池] 开始将排名前十的股票加入强势池,导入日期: {}", parsedRecordDate);
+        addTopTenToStrongPool(parsedRecordDate);
+
+        // 调用数据补全功能,将历史数据补全到stock_pool表
+        try {
+            log.info("[数据补全] 开始补全历史数据到stock_pool表,导入日期: {}", parsedRecordDate);
+            String completeResult = stockPoolService.completeHistoryData(parsedRecordDate);
+            log.info("[数据补全] 补全结果: {}", completeResult);
+        } catch (Exception e) {
+            log.error("[数据补全] 补全历史数据失败: {}", e.getMessage(), e);
         }
 
         if (failureNum > 0) {
@@ -450,7 +467,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
     }
 
     /**
-     * 将排名前十的股票加入强势池(覆盖原有数据
+     * 将排名前十的股票加入强势池(将之前的设为历史有效
      */
     private void addTopTenToStrongPool(LocalDate recordDate) {
         try {
@@ -470,15 +487,43 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                 .map(h -> h.getStockCode() + "-" + h.getStockName())
                 .collect(java.util.stream.Collectors.joining(", ")));
 
-            // 2. 清空原有的强势池数据(poolType=2)
-            LambdaQueryWrapper<StockPool> deleteLqw = Wrappers.lambdaQuery();
-            deleteLqw.eq(StockPool::getPoolType, 2);
-            int deletedCount = stockPoolMapper.delete(deleteLqw);
-            log.info("[强势池] 清空原有强势池数据,删除 {} 条", deletedCount);
+            // 2. 查询当天已存在的强势池数据
+            LambdaQueryWrapper<StockPool> todayLqw = Wrappers.lambdaQuery();
+            todayLqw.eq(StockPool::getPoolType, 2)
+                    .eq(StockPool::getAddDate, recordDate)
+                    .eq(StockPool::getStatus, 2);
+            List<StockPool> todayPools = stockPoolMapper.selectList(todayLqw);
+            Map<String, StockPool> todayPoolMap = todayPools.stream()
+                .collect(java.util.stream.Collectors.toMap(StockPool::getStockCode, p -> p));
+
+            // 3. 收集前十名的股票代码
+            java.util.Set<String> topTenCodes = topTen.stream()
+                .map(StockPoolHistory::getStockCode)
+                .collect(java.util.stream.Collectors.toSet());
+
+            // 4. 将当天存在但不在前十名中的股票状态改为1
+            for (StockPool pool : todayPools) {
+                if (!topTenCodes.contains(pool.getStockCode())) {
+                    pool.setStatus(1);
+                    stockPoolMapper.updateById(pool);
+                }
+            }
+
+            // 5. 将其他日期的强势池数据状态改为1
+            LambdaQueryWrapper<StockPool> otherDaysLqw = Wrappers.lambdaQuery();
+            otherDaysLqw.eq(StockPool::getPoolType, 2)
+                        .eq(StockPool::getStatus, 2)
+                        .ne(StockPool::getAddDate, recordDate);
+            List<StockPool> otherDaysPools = stockPoolMapper.selectList(otherDaysLqw);
+            for (StockPool pool : otherDaysPools) {
+                pool.setStatus(1);
+                stockPoolMapper.updateById(pool);
+            }
 
-            // 3. 将前十名加入强势池
+            // 6. 处理前十名:更新或插入
             Long adminId = LoginHelper.getUserId();
             List<StockPool> toInsert = new ArrayList<>();
+            int updateCount = 0;
 
             for (StockPoolHistory history : topTen) {
                 // 优先使用 closePrice,如果为空则使用 dayClosePrice
@@ -493,20 +538,39 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                     continue;
                 }
 
-                StockPool pool = new StockPool();
-                pool.setStockCode(history.getStockCode());
-                pool.setStockName(history.getStockName());
-                pool.setPoolType(2);  // 强势池
-                pool.setAddPrice(addPrice);
-                pool.setAddDate(recordDate);
-                pool.setStatus(1);  // 有效
-                pool.setAdminId(adminId);
-                toInsert.add(pool);
+                String stockCode = history.getStockCode();
+                StockPool existingPool = todayPoolMap.get(stockCode);
+
+                if (existingPool != null) {
+                    // 当天已存在该股票,更新数据
+                    existingPool.setStockName(history.getStockName());
+                    existingPool.setAddPrice(addPrice);
+                    existingPool.setNextDayGain(history.getHighTrend());
+                    existingPool.setStatus(2);
+                    existingPool.setAdminId(adminId);
+                    stockPoolMapper.updateById(existingPool);
+                    updateCount++;
+                } else {
+                    // 当天不存在该股票,插入新记录
+                    StockPool pool = new StockPool();
+                    pool.setStockCode(stockCode);
+                    pool.setStockName(history.getStockName());
+                    pool.setPoolType(2);  // 强势池
+                    pool.setAddPrice(addPrice);
+                    pool.setAddDate(recordDate);
+                    pool.setNextDayGain(history.getHighTrend());  // 设置最高涨幅
+                    pool.setStatus(2);  // 当前有效
+                    pool.setAdminId(adminId);
+                    toInsert.add(pool);
+                }
             }
 
             if (!toInsert.isEmpty()) {
                 stockPoolMapper.insertBatch(toInsert);
-                log.info("[强势池] 成功加入 {} 只股票到强势池", toInsert.size());
+                log.info("[强势池] 成功插入 {} 只股票到强势池", toInsert.size());
+            }
+            if (updateCount > 0) {
+                log.info("[强势池] 成功更新 {} 只股票到强势池", updateCount);
             }
 
         } catch (Exception e) {
@@ -514,6 +578,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         }
     }
 
+
     /**
      * 从 Map 中获取字符串值
      */
@@ -531,15 +596,15 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
             value = value.substring(1);
             value = value.replace("\"", "").replace("'", "");
         }
-        
+
         // 尝试修复编码问题(如果检测到乱码)
         if (containsGarbledText(value)) {
             value = tryFixEncoding(value);
         }
-        
+
         return value.isEmpty() ? null : value;
     }
-    
+
     /**
      * 检测是否包含乱码(简单检测)
      */
@@ -560,7 +625,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         // 如果超过30%的字符是特殊字符,可能是乱码
         return specialCharCount > text.length() * 0.3;
     }
-    
+
     /**
      * 尝试修复编码问题
      */
@@ -576,7 +641,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
                 log.debug("编码修复成功: [{}] -> [{}]", text, gbkText);
                 return gbkText;
             }
-            
+
             // 尝试将 UTF-8 编码的字符串转换为 GBK
             bytes = text.getBytes(java.nio.charset.StandardCharsets.UTF_8);
             gbkText = new String(bytes, java.nio.charset.Charset.forName("GBK"));
@@ -589,7 +654,7 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         }
         return text;
     }
-    
+
     /**
      * 从 Map 中获取 BigDecimal 值
      */

+ 144 - 7
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/service/impl/StockPoolServiceImpl.java

@@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.yingpai.stock.domain.StockPool;
+import com.yingpai.stock.domain.StockPoolHistory;
 import com.yingpai.stock.domain.StockQuoteData;
 import com.yingpai.stock.domain.bo.StockPoolBo;
 import com.yingpai.stock.domain.vo.StockPoolVo;
+import com.yingpai.stock.mapper.StockPoolHistoryMapper;
 import com.yingpai.stock.mapper.StockPoolMapper;
 import com.yingpai.stock.service.IStockPoolService;
 import com.yingpai.stock.service.IStockQuoteService;
@@ -37,6 +39,7 @@ public class StockPoolServiceImpl implements IStockPoolService {
 
     private final StockPoolMapper baseMapper;
     private final IStockQuoteService stockQuoteService;
+    private final StockPoolHistoryMapper stockPoolHistoryMapper;
 
     @Override
     public TableDataInfo<StockPoolVo> queryPageList(StockPoolBo bo, PageQuery pageQuery) {
@@ -79,31 +82,35 @@ public class StockPoolServiceImpl implements IStockPoolService {
             .last("LIMIT 1");
         StockPool existing = baseMapper.selectOne(checkWrapper);
 
+        LocalDate today = LocalDate.now();
+
         if (existing != null) {
-            if (existing.getStatus() == 1) {
+            if (existing.getStatus() == 2) {
                 // 已经在池中,无需操作
                 return true;
             }
             // 已移除的记录,恢复状态
-            existing.setStatus(1);
+            existing.setStatus(2);
             existing.setAdminId(LoginHelper.getUserId());
             existing.setUpdateTime(LocalDateTime.now());
-            existing.setAddDate(LocalDate.now());
+            existing.setAddDate(today);
             // 更新入池价格
             BigDecimal price = fetchCurrentPrice(bo.getStockCode());
             existing.setAddPrice(price != null ? price : BigDecimal.ZERO);
+            // 从历史信息中查询当天的收盘价
+            existing.setClosePrice(fetchClosePriceFromHistory(bo.getStockCode(), today));
             return baseMapper.updateById(existing) > 0;
         }
 
         // 不存在,新增
         StockPool entity = BeanUtil.toBean(bo, StockPool.class);
-        entity.setStatus(1);
+        entity.setStatus(2);
         entity.setAdminId(LoginHelper.getUserId());
         entity.setCreateTime(LocalDateTime.now());
         entity.setUpdateTime(LocalDateTime.now());
 
         if (entity.getAddDate() == null) {
-            entity.setAddDate(LocalDate.now());
+            entity.setAddDate(today);
         }
 
         // 如果没有传入价格,尝试获取实时价格
@@ -112,6 +119,9 @@ public class StockPoolServiceImpl implements IStockPoolService {
             entity.setAddPrice(price != null ? price : BigDecimal.ZERO);
         }
 
+        // 从历史信息中查询当天的收盘价
+        entity.setClosePrice(fetchClosePriceFromHistory(entity.getStockCode(), today));
+
         boolean flag = baseMapper.insert(entity) > 0;
         if (flag) {
             bo.setId(entity.getId());
@@ -148,7 +158,7 @@ public class StockPoolServiceImpl implements IStockPoolService {
         LambdaQueryWrapper<StockPool> lqw = Wrappers.lambdaQuery();
         lqw.eq(StockPool::getStockCode, stockCode)
             .eq(StockPool::getPoolType, poolType)
-            .eq(StockPool::getStatus, 1);
+            .eq(StockPool::getStatus, 2);
         StockPool pool = baseMapper.selectOne(lqw);
         if (pool != null) {
             pool.setStatus(0);
@@ -166,7 +176,12 @@ public class StockPoolServiceImpl implements IStockPoolService {
         lqw.like(StringUtils.isNotBlank(bo.getStockCode()), StockPool::getStockCode, bo.getStockCode());
         lqw.like(StringUtils.isNotBlank(bo.getStockName()), StockPool::getStockName, bo.getStockName());
         lqw.eq(ObjectUtil.isNotNull(bo.getPoolType()), StockPool::getPoolType, bo.getPoolType());
-        lqw.eq(ObjectUtil.isNotNull(bo.getStatus()), StockPool::getStatus, bo.getStatus());
+        // 如果指定了status则按指定值查询,否则默认查询status为1和2的记录
+        if (ObjectUtil.isNotNull(bo.getStatus())) {
+            lqw.eq(StockPool::getStatus, bo.getStatus());
+        } else {
+            lqw.in(StockPool::getStatus, 1, 2);
+        }
         lqw.ge(ObjectUtil.isNotNull(bo.getStartDate()), StockPool::getAddDate, bo.getStartDate());
         lqw.le(ObjectUtil.isNotNull(bo.getEndDate()), StockPool::getAddDate, bo.getEndDate());
         lqw.orderByDesc(StockPool::getAddDate);
@@ -221,4 +236,126 @@ public class StockPoolServiceImpl implements IStockPoolService {
         StockQuoteData quote = quotes.get(code);
         return quote != null ? quote.getCurrentPrice() : null;
     }
+
+    /**
+     * 从历史信息中查询收盘价
+     */
+    private BigDecimal fetchClosePriceFromHistory(String stockCode, LocalDate recordDate) {
+        LambdaQueryWrapper<StockPoolHistory> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(StockPoolHistory::getStockCode, stockCode)
+               .eq(StockPoolHistory::getRecordDate, recordDate)
+               .last("LIMIT 1");
+        StockPoolHistory history = stockPoolHistoryMapper.selectOne(wrapper);
+        return history != null ? history.getClosePrice() : null;
+    }
+
+    @Override
+    public String completeHistoryData(LocalDate importDate) throws Exception {
+        log.info("[补全历史数据] 开始补全,导入日期: {}", importDate);
+
+        // 查找离导入日期最近的一天(向前查找最多7天)
+        LocalDate previousDate = null;
+        List<StockPool> previousDayData = new java.util.ArrayList<>();
+        for (int i = 1; i <= 7; i++) {
+            LocalDate checkDate = importDate.minusDays(i);
+            List<StockPool> data = baseMapper.selectByDateAndType(checkDate, 1);
+            if (!data.isEmpty()) {
+                previousDate = checkDate;
+                previousDayData = data;
+                log.info("[补全历史数据] 找到最近的交易日: {}", previousDate);
+                break;
+            }
+        }
+
+        if (previousDate == null) {
+            log.warn("[补全历史数据] 未找到最近7天内的交易数据");
+        }
+
+        // 查询当天的超短池数据
+        List<StockPool> currentDayData = baseMapper.selectByDateAndType(importDate, 1);
+        if (currentDayData.isEmpty()) {
+            String message = "当天(" + importDate + ")没有超短池数据,无法补全";
+            log.warn("[补全历史数据] {}", message);
+            return message;
+        }
+
+        // 查询当天的历史数据(从stock_pool_history表)
+        LambdaQueryWrapper<StockPoolHistory> historyWrapper = Wrappers.lambdaQuery();
+        historyWrapper.eq(StockPoolHistory::getRecordDate, importDate);
+        List<StockPoolHistory> historyData = stockPoolHistoryMapper.selectList(historyWrapper);
+
+        // 将历史数据转换为Map,key为股票代码
+        Map<String, StockPoolHistory> historyMap = historyData.stream()
+            .collect(Collectors.toMap(StockPoolHistory::getStockCode, h -> h));
+
+        int currentDayUpdated = 0;
+        int previousDayUpdated = 0;
+
+        // 1. 补全当天的收盘价(从stock_pool_history表获取)
+        for (StockPool currentPool : currentDayData) {
+            StockPoolHistory history = historyMap.get(currentPool.getStockCode());
+            if (history != null && history.getClosePrice() != null) {
+                if (currentPool.getClosePrice() == null) {
+                    currentPool.setClosePrice(history.getClosePrice());
+                    currentPool.setUpdateTime(LocalDateTime.now());
+                    baseMapper.updateById(currentPool);
+                    currentDayUpdated++;
+                    log.debug("[补全历史数据] 股票 {} 当天收盘价补全: {}",
+                        currentPool.getStockCode(), history.getClosePrice());
+                }
+            }
+        }
+
+        // 2. 补全前一天的隔日最高价和隔日涨幅
+        if (!previousDayData.isEmpty()) {
+            for (StockPool previousPool : previousDayData) {
+                String stockCode = previousPool.getStockCode();
+                StockPoolHistory currentHistory = historyMap.get(stockCode);
+
+                if (currentHistory == null) {
+                    log.debug("[补全历史数据] 股票 {} 在当天历史数据中没有记录,跳过前一天补全", stockCode);
+                    continue;
+                }
+
+                // 补全前一天的隔日最高价(使用当天历史数据的最高价)
+                BigDecimal nextDayHigh = currentHistory.getDayHighestPrice();
+                if (nextDayHigh == null) {
+                    // 如果没有最高价,使用收盘价
+                    nextDayHigh = currentHistory.getClosePrice();
+                }
+
+                if (nextDayHigh == null) {
+                    log.debug("[补全历史数据] 股票 {} 当天历史数据中没有最高价和收盘价,跳过", stockCode);
+                    continue;
+                }
+
+                // 计算隔日涨幅:(当天最高价 - 前一天收盘价) / 前一天收盘价 * 100
+                BigDecimal nextDayGain = BigDecimal.ZERO;
+                BigDecimal previousClosePrice = previousPool.getClosePrice();
+
+                if (previousClosePrice != null && previousClosePrice.compareTo(BigDecimal.ZERO) > 0) {
+                    nextDayGain = nextDayHigh.subtract(previousClosePrice)
+                        .divide(previousClosePrice, 4, RoundingMode.HALF_UP)
+                        .multiply(new BigDecimal("100"))
+                        .setScale(2, RoundingMode.HALF_UP);
+                }
+
+                // 更新前一天的数据
+                previousPool.setNextDayHigh(nextDayHigh);
+                previousPool.setNextDayGain(nextDayGain);
+                previousPool.setUpdateTime(LocalDateTime.now());
+
+                baseMapper.updateById(previousPool);
+                previousDayUpdated++;
+
+                log.debug("[补全历史数据] 股票 {} 前一天补全成功,隔日最高: {}, 隔日涨幅: {}%",
+                    stockCode, nextDayHigh, nextDayGain);
+            }
+        }
+
+        String message = String.format("补全完成!当天收盘价补全 %d 条,前一天隔日数据补全 %d 条",
+            currentDayUpdated, previousDayUpdated);
+        log.info("[补全历史数据] {}", message);
+        return message;
+    }
 }