Przeglądaj źródła

联系我们功能添加

Zhangbw 2 miesięcy temu
rodzic
commit
bffdafde3b

+ 2 - 1
ruoyi-admin/src/main/java/org/dromara/DromaraApplication.java

@@ -5,13 +5,14 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
 import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * 启动程序
  *
  * @author Lion Li
  */
-
+@EnableScheduling
 @SpringBootApplication
 @ComponentScan(basePackages = {"org.dromara", "com.yingpai"})
 @MapperScan({"org.dromara.**.mapper", "com.yingpai.**.mapper"})

+ 1 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -142,6 +142,7 @@ tenant:
     - user_subscription
     - payment_order
     - stock_pool_history
+    - user_feedback
 
 # MyBatisPlus配置
 # https://baomidou.com/config/

+ 4 - 4
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/controller/PaymentOrderController.java

@@ -59,12 +59,12 @@ public class PaymentOrderController extends BaseController {
     }
 
     /**
-     * 删除订单
+     * 删除订单(支持单个和批量删除)
      */
     @SaCheckPermission("miniapp:order:remove")
     @Log(title = "订单管理", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{id}")
-    public R<Void> remove(@PathVariable Long id) {
-        return toAjax(orderService.deleteById(id));
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(orderService.deleteByIds(List.of(ids)));
     }
 }

+ 70 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/controller/UserFeedbackController.java

@@ -0,0 +1,70 @@
+package com.yingpai.miniapp.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.yingpai.miniapp.domain.bo.UserFeedbackBo;
+import com.yingpai.miniapp.domain.vo.UserFeedbackVo;
+import com.yingpai.miniapp.service.IUserFeedbackService;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户反馈管理
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/miniapp/feedback")
+public class UserFeedbackController extends BaseController {
+
+    private final IUserFeedbackService feedbackService;
+
+    /**
+     * 查询用户反馈列表
+     */
+    @SaCheckPermission("miniapp:feedback:list")
+    @GetMapping("/list")
+    public TableDataInfo<UserFeedbackVo> list(UserFeedbackBo bo, PageQuery pageQuery) {
+        return feedbackService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出用户反馈列表
+     */
+    @SaCheckPermission("miniapp:feedback:export")
+    @Log(title = "用户反馈", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(UserFeedbackBo bo, HttpServletResponse response) {
+        List<UserFeedbackVo> list = feedbackService.queryList(bo);
+        ExcelUtil.exportExcel(list, "用户反馈数据", UserFeedbackVo.class, response);
+    }
+
+    /**
+     * 获取用户反馈详情
+     */
+    @SaCheckPermission("miniapp:feedback:query")
+    @GetMapping("/{id}")
+    public R<UserFeedbackVo> getInfo(@PathVariable Long id) {
+        return R.ok(feedbackService.queryById(id));
+    }
+
+    /**
+     * 删除用户反馈(支持单个和批量删除)
+     */
+    @SaCheckPermission("miniapp:feedback:remove")
+    @Log(title = "用户反馈", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(feedbackService.deleteByIds(List.of(ids)));
+    }
+}

+ 31 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/domain/UserFeedback.java

@@ -0,0 +1,31 @@
+package com.yingpai.miniapp.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 用户反馈实体类
+ */
+@Data
+@TableName("user_feedback")
+public class UserFeedback {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 反馈内容 */
+    private String content;
+
+    /** 图片URL列表,多个图片用英文逗号分隔 */
+    private String images;
+
+    /** 创建时间 */
+    private LocalDateTime createTime;
+}

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

@@ -0,0 +1,22 @@
+package com.yingpai.miniapp.domain.bo;
+
+import lombok.Data;
+
+/**
+ * 用户反馈查询BO
+ */
+@Data
+public class UserFeedbackBo {
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 用户昵称 */
+    private String nickname;
+
+    /** 用户手机号 */
+    private String phone;
+
+    /** 反馈内容(模糊查询) */
+    private String content;
+}

+ 34 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/domain/vo/UserFeedbackVo.java

@@ -0,0 +1,34 @@
+package com.yingpai.miniapp.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 用户反馈VO
+ */
+@Data
+public class UserFeedbackVo {
+
+    @ExcelProperty("反馈ID")
+    private Long id;
+
+    @ExcelProperty("用户ID")
+    private Long userId;
+
+    @ExcelProperty("用户昵称")
+    private String nickname;
+
+    @ExcelProperty("用户手机号")
+    private String phone;
+
+    @ExcelProperty("反馈内容")
+    private String content;
+
+    @ExcelProperty("图片URL")
+    private String images;
+
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+}

+ 12 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/mapper/UserFeedbackMapper.java

@@ -0,0 +1,12 @@
+package com.yingpai.miniapp.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yingpai.miniapp.domain.UserFeedback;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户反馈Mapper接口
+ */
+@Mapper
+public interface UserFeedbackMapper extends BaseMapper<UserFeedback> {
+}

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

@@ -31,4 +31,9 @@ public interface IPaymentOrderService {
      * 删除订单
      */
     Boolean deleteById(Long id);
+
+    /**
+     * 批量删除订单
+     */
+    Boolean deleteByIds(List<Long> ids);
 }

+ 39 - 0
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/service/IUserFeedbackService.java

@@ -0,0 +1,39 @@
+package com.yingpai.miniapp.service;
+
+import com.yingpai.miniapp.domain.bo.UserFeedbackBo;
+import com.yingpai.miniapp.domain.vo.UserFeedbackVo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+import java.util.List;
+
+/**
+ * 用户反馈Service接口
+ */
+public interface IUserFeedbackService {
+
+    /**
+     * 分页查询反馈列表
+     */
+    TableDataInfo<UserFeedbackVo> queryPageList(UserFeedbackBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询反馈列表
+     */
+    List<UserFeedbackVo> queryList(UserFeedbackBo bo);
+
+    /**
+     * 根据ID查询反馈
+     */
+    UserFeedbackVo queryById(Long id);
+
+    /**
+     * 删除反馈
+     */
+    Boolean deleteById(Long id);
+
+    /**
+     * 批量删除反馈
+     */
+    Boolean deleteByIds(List<Long> ids);
+}

+ 6 - 1
ruoyi-modules/yp-miniapp/src/main/java/com/yingpai/miniapp/service/impl/PaymentOrderServiceImpl.java

@@ -59,7 +59,12 @@ public class PaymentOrderServiceImpl implements IPaymentOrderService {
     public Boolean deleteById(Long id) {
         return orderMapper.deleteById(id) > 0;
     }
-    
+
+    @Override
+    public Boolean deleteByIds(List<Long> ids) {
+        return orderMapper.deleteBatchIds(ids) > 0;
+    }
+
     private LambdaQueryWrapper<PaymentOrder> buildQueryWrapper(PaymentOrderBo bo) {
         LambdaQueryWrapper<PaymentOrder> wrapper = new LambdaQueryWrapper<>();
         wrapper.like(StringUtils.isNotBlank(bo.getOrderNo()), PaymentOrder::getOrderNo, bo.getOrderNo());

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

@@ -0,0 +1,109 @@
+package com.yingpai.miniapp.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yingpai.miniapp.domain.UserFeedback;
+import com.yingpai.miniapp.domain.bo.UserFeedbackBo;
+import com.yingpai.miniapp.domain.vo.UserFeedbackVo;
+import com.yingpai.miniapp.mapper.MiniappUserMapper;
+import com.yingpai.miniapp.mapper.UserFeedbackMapper;
+import com.yingpai.miniapp.service.IUserFeedbackService;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户反馈Service实现
+ */
+@Service
+@RequiredArgsConstructor
+public class UserFeedbackServiceImpl implements IUserFeedbackService {
+
+    private final UserFeedbackMapper feedbackMapper;
+    private final MiniappUserMapper userMapper;
+
+    @Override
+    public TableDataInfo<UserFeedbackVo> queryPageList(UserFeedbackBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<UserFeedback> wrapper = buildQueryWrapper(bo);
+        Page<UserFeedback> page = feedbackMapper.selectPage(pageQuery.build(), wrapper);
+
+        List<UserFeedbackVo> voList = page.getRecords().stream()
+                .map(this::toVo)
+                .collect(Collectors.toList());
+
+        Page<UserFeedbackVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        voPage.setRecords(voList);
+        return TableDataInfo.build(voPage);
+    }
+
+    @Override
+    public List<UserFeedbackVo> queryList(UserFeedbackBo bo) {
+        LambdaQueryWrapper<UserFeedback> wrapper = buildQueryWrapper(bo);
+        return feedbackMapper.selectList(wrapper).stream()
+                .map(this::toVo)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public UserFeedbackVo queryById(Long id) {
+        UserFeedback feedback = feedbackMapper.selectById(id);
+        return feedback != null ? toVo(feedback) : null;
+    }
+
+    @Override
+    public Boolean deleteById(Long id) {
+        return feedbackMapper.deleteById(id) > 0;
+    }
+
+    @Override
+    public Boolean deleteByIds(List<Long> ids) {
+        return feedbackMapper.deleteBatchIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<UserFeedback> buildQueryWrapper(UserFeedbackBo bo) {
+        LambdaQueryWrapper<UserFeedback> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(bo.getUserId() != null, UserFeedback::getUserId, bo.getUserId());
+        wrapper.like(StringUtils.isNotBlank(bo.getContent()), UserFeedback::getContent, bo.getContent());
+        wrapper.orderByDesc(UserFeedback::getCreateTime);
+
+        // 如果有昵称或手机号查询,需要先查用户ID
+        if (StringUtils.isNotBlank(bo.getNickname()) || StringUtils.isNotBlank(bo.getPhone())) {
+            List<Long> userIds = userMapper.selectList(new LambdaQueryWrapper<com.yingpai.miniapp.domain.MiniappUser>()
+                    .like(StringUtils.isNotBlank(bo.getNickname()), com.yingpai.miniapp.domain.MiniappUser::getNickname, bo.getNickname())
+                    .like(StringUtils.isNotBlank(bo.getPhone()), com.yingpai.miniapp.domain.MiniappUser::getPhone, bo.getPhone()))
+                    .stream()
+                    .map(com.yingpai.miniapp.domain.MiniappUser::getId)
+                    .collect(Collectors.toList());
+            if (userIds.isEmpty()) {
+                wrapper.eq(UserFeedback::getUserId, -1L); // 无匹配用户
+            } else {
+                wrapper.in(UserFeedback::getUserId, userIds);
+            }
+        }
+
+        return wrapper;
+    }
+
+    private UserFeedbackVo toVo(UserFeedback feedback) {
+        UserFeedbackVo vo = new UserFeedbackVo();
+        vo.setId(feedback.getId());
+        vo.setUserId(feedback.getUserId());
+        vo.setContent(feedback.getContent());
+        vo.setImages(feedback.getImages());
+        vo.setCreateTime(feedback.getCreateTime());
+
+        // 查询用户信息
+        var user = userMapper.selectById(feedback.getUserId());
+        if (user != null) {
+            vo.setNickname(user.getNickname());
+            vo.setPhone(user.getPhone());
+        }
+
+        return vo;
+    }
+}

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

@@ -8,10 +8,12 @@ import cn.idev.excel.read.listener.ReadListener;
 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.bo.StockPoolHistoryBo;
 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 lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -43,6 +45,7 @@ import java.util.Map;
 public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
 
     private final StockPoolHistoryMapper baseMapper;
+    private final StockPoolMapper stockPoolMapper;
 
     @Override
     public TableDataInfo<StockPoolHistoryVo> queryPageList(StockPoolHistoryBo bo, PageQuery pageQuery) {
@@ -429,8 +432,14 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
             log.info("批量更新完成: {} 条", toUpdateList.size());
         }
 
+        // 如果导入日期是当天,将排名前十的股票加入强势池
+        if (parsedRecordDate.equals(LocalDate.now())) {
+            log.info("[强势池] 检测到导入日期为当天,开始将排名前十的股票加入强势池");
+            addTopTenToStrongPool(parsedRecordDate);
+        }
+
         if (failureNum > 0) {
-            String message = String.format("导入完成!成功 %d 条(其中更新 %d 条),失败 %d 条。详细错误信息请查看服务器日志。", 
+            String message = String.format("导入完成!成功 %d 条(其中更新 %d 条),失败 %d 条。详细错误信息请查看服务器日志。",
                 successNum, updateNum, failureNum);
             log.error("导入失败详情:{}", failureMsg.toString());
             return message;
@@ -440,6 +449,71 @@ public class StockPoolHistoryServiceImpl implements IStockPoolHistoryService {
         }
     }
 
+    /**
+     * 将排名前十的股票加入强势池(覆盖原有数据)
+     */
+    private void addTopTenToStrongPool(LocalDate recordDate) {
+        try {
+            // 1. 查询当天按强度评分排名前十的股票(Excel中的前10行)
+            LambdaQueryWrapper<StockPoolHistory> lqw = Wrappers.lambdaQuery();
+            lqw.eq(StockPoolHistory::getRecordDate, recordDate)
+                .orderByAsc(StockPoolHistory::getId)  // 按ID升序,获取Excel中的前10行
+                .last("LIMIT 10");
+            List<StockPoolHistory> topTen = baseMapper.selectList(lqw);
+
+            if (topTen.isEmpty()) {
+                log.warn("[强势池] 未找到当天的历史数据,无法加入强势池");
+                return;
+            }
+
+            log.info("[强势池] 查询到排名前十的股票: {}", topTen.stream()
+                .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);
+
+            // 3. 将前十名加入强势池
+            Long adminId = LoginHelper.getUserId();
+            List<StockPool> toInsert = new ArrayList<>();
+
+            for (StockPoolHistory history : topTen) {
+                // 优先使用 closePrice,如果为空则使用 dayClosePrice
+                BigDecimal addPrice = history.getClosePrice();
+                if (addPrice == null) {
+                    addPrice = history.getDayClosePrice();
+                }
+
+                // 如果两个价格都为空,跳过该记录
+                if (addPrice == null) {
+                    log.warn("[强势池] 股票 {} 的收盘价和当天收盘价都为空,跳过加入强势池", history.getStockCode());
+                    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);
+            }
+
+            if (!toInsert.isEmpty()) {
+                stockPoolMapper.insertBatch(toInsert);
+                log.info("[强势池] 成功加入 {} 只股票到强势池", toInsert.size());
+            }
+
+        } catch (Exception e) {
+            log.error("[强势池] 加入强势池失败: {}", e.getMessage(), e);
+        }
+    }
+
     /**
      * 从 Map 中获取字符串值
      */

+ 49 - 38
ruoyi-modules/yp-stock/src/main/java/com/yingpai/stock/service/impl/StockQuoteServiceImpl.java

@@ -36,7 +36,8 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
         .build();
 
     private static final int BATCH_SIZE = 50;
-    private static final String BATCH_QUOTE_URL = "http://push2.eastmoney.com/api/qt/ulist.np/get?fltt=2&fields=f12,f14,f2,f3,f4,f5,f6,f8,f18&secids=%s";
+    // 腾讯股票行情接口
+    private static final String BATCH_QUOTE_URL = "http://qt.gtimg.cn/q=%s";
 
     public StockQuoteServiceImpl(@Qualifier("stockQuoteExecutor") ThreadPoolTaskExecutor executor) {
         this.executor = executor;
@@ -94,45 +95,57 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
     private Map<String, StockQuoteData> fetchBatchQuotes(List<String> batch) {
         Map<String, StockQuoteData> result = new HashMap<>();
         try {
-            String secids = batch.stream()
-                .map(this::getSecId)
+            // 构建腾讯API格式的股票代码列表:sh600519,sz000001
+            String codes = batch.stream()
+                .map(this::getTencentCode)
                 .filter(Objects::nonNull)
                 .collect(Collectors.joining(","));
 
-            if (secids.isEmpty()) return result;
+            if (codes.isEmpty()) return result;
 
             HttpRequest request = HttpRequest.newBuilder()
-                .uri(URI.create(String.format(BATCH_QUOTE_URL, secids)))
-                .header("User-Agent", "Mozilla/5.0")
+                .uri(URI.create(String.format(BATCH_QUOTE_URL, codes)))
                 .GET()
                 .build();
 
             HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
 
             if (response.statusCode() == 200) {
-                JsonNode root = objectMapper.readTree(response.body());
-                JsonNode data = root.path("data").path("diff");
-
-                if (data != null && data.isArray()) {
-                    for (JsonNode item : data) {
-                        String code = item.path("f12").asText();
-                        if (code == null || code.isEmpty()) continue;
-
-                        StockQuoteData quote = StockQuoteData.builder()
-                            .stockCode(code)
-                            .currentPrice(parseBigDecimal(item, "f2"))
-                            .yesterdayClose(parseBigDecimal(item, "f18"))
-                            .changeAmount(parseBigDecimal(item, "f4"))
-                            .changePercent(parseBigDecimal(item, "f3"))
-                            .turnoverRate(parseBigDecimal(item, "f8"))
-                            .build();
-
-                        BigDecimal tradeAmount = parseBigDecimal(item, "f6");
-                        if (tradeAmount != null) {
-                            quote.setTradeAmount(tradeAmount.divide(new BigDecimal("100000000"), 2, RoundingMode.HALF_UP));
+                String body = response.body();
+                String[] lines = body.split(";");
+
+                for (String line : lines) {
+                    line = line.trim();
+                    if (line.isEmpty()) continue;
+
+                    // 解析腾讯API返回格式: v_sh600519="1~贵州茅台~600519~2076.91~..."
+                    int startIndex = line.indexOf("=\"");
+                    int endIndex = line.lastIndexOf("\"");
+
+                    if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
+                        String data = line.substring(startIndex + 2, endIndex);
+                        String[] fields = data.split("~");
+
+                        if (fields.length > 38) {
+                            String code = fields[2];  // 股票代码
+
+                            StockQuoteData quote = StockQuoteData.builder()
+                                .stockCode(code)
+                                .currentPrice(parseBigDecimal(fields[3]))      // 当前价格 [3]
+                                .yesterdayClose(parseBigDecimal(fields[4]))    // 昨收 [4]
+                                .changeAmount(parseBigDecimal(fields[31]))     // 涨跌额 [31]
+                                .changePercent(parseBigDecimal(fields[32]))    // 涨跌幅 [32]
+                                .turnoverRate(parseBigDecimal(fields[38]))     // 换手率 [38]
+                                .build();
+
+                            // 成交额(万元)转换为亿
+                            BigDecimal amount = parseBigDecimal(fields[37]);
+                            if (amount != null) {
+                                quote.setTradeAmount(amount.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP));
+                            }
+
+                            result.put(code, quote);
                         }
-
-                        result.put(code, quote);
                     }
                 }
             }
@@ -154,24 +167,22 @@ public class StockQuoteServiceImpl implements IStockQuoteService {
     }
 
     /**
-     * 获取secId(东方财富格式
+     * 获取腾讯API格式的股票代码(sh600519, sz000001
      */
-    private String getSecId(String code) {
+    private String getTencentCode(String code) {
         if (code == null || code.length() != 6) return null;
-        if (code.startsWith("6")) return "1." + code;
-        if (code.startsWith("0") || code.startsWith("3")) return "0." + code;
+        if (code.startsWith("6")) return "sh" + code;
+        if (code.startsWith("0") || code.startsWith("3")) return "sz" + code;
         return null;
     }
 
     /**
-     * 解析BigDecimal
+     * 解析BigDecimal(从字符串)
      */
-    private BigDecimal parseBigDecimal(JsonNode node, String field) {
-        if (!node.has(field)) return null;
-        JsonNode value = node.get(field);
-        if (value == null || value.isNull() || "-".equals(value.asText())) return null;
+    private BigDecimal parseBigDecimal(String value) {
+        if (value == null || value.isEmpty() || "-".equals(value)) return null;
         try {
-            return new BigDecimal(value.asText());
+            return new BigDecimal(value);
         } catch (Exception e) {
             return null;
         }