Zhangbw před 2 měsíci
rodič
revize
fa9653dc76

+ 7 - 1
.claude/settings.local.json

@@ -10,7 +10,13 @@
       "Bash(taskkill:*)",
       "Bash(curl:*)",
       "Bash(git restore:*)",
-      "Bash(npm run dev:mp-weixin:*)"
+      "Bash(npm run dev:mp-weixin:*)",
+      "Bash(stat:*)",
+      "Bash(grep:*)",
+      "Bash(npm run build:h5:*)",
+      "Bash(npm install:*)",
+      "Bash(find:*)",
+      "Bash(git checkout:*)"
     ]
   }
 }

+ 1 - 1
src/main/java/com/yingpai/gupiao/config/WxConfig.java

@@ -5,7 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 /**
- * 微信小程序配置类
+ * 微信配置类
  * 从application.properties读取微信配置
  */
 @Data

+ 0 - 110
src/main/java/com/yingpai/gupiao/controller/AuthController.java

@@ -1,110 +0,0 @@
-package com.yingpai.gupiao.controller;
-
-import com.yingpai.gupiao.domain.dto.*;
-import com.yingpai.gupiao.domain.vo.LoginVO;
-import com.yingpai.gupiao.domain.vo.Result;
-import com.yingpai.gupiao.domain.vo.WxLoginCheckVO;
-import com.yingpai.gupiao.service.AuthService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * 认证控制器
- * 处理用户登录、注册相关请求
- *
- */
-@Slf4j
-@RestController
-@RequiredArgsConstructor
-@RequestMapping("/v1/auth/wx")
-public class AuthController {
-    
-    private final AuthService authService;
-    
-    /**
-     * 第一步:微信静默登录(检查是否为老用户)
-     * 接口路径:POST /v1/auth/wx/silent-login
-     * 
-     * @param dto 包含 loginCode
-     * @return 老用户返回 { isSign: "true", token },新用户返回 { isSign: "false" }
-     */
-    @PostMapping("/silent-login")
-    public Result<WxLoginCheckVO> wxSilentLogin(@RequestBody WxSilentLoginDTO dto) {
-        log.info("【第一步】微信静默登录,loginCode: {}", dto.getLoginCode());
-        
-        if (dto.getLoginCode() == null || dto.getLoginCode().isEmpty()) {
-            return Result.error("登录code不能为空");
-        }
-        
-        try {
-            WxLoginCheckVO result = authService.wxSilentLogin(dto.getLoginCode());
-            return Result.success(result);
-        } catch (Exception e) {
-            log.error("微信静默登录失败", e);
-            return Result.error("登录失败:" + e.getMessage());
-        }
-    }
-    
-    /**
-     * 第二步:手机号授权验证(新用户)
-     * 接口路径:POST /v1/auth/wx/phone-verify
-     * 
-     * @param dto 包含 loginCode, encryptedData, iv
-     * @return 已注册返回 { isSign: "true", token },未注册返回 { isSign: "false", openid, unionid, phoneNumber }
-     */
-    @PostMapping("/phone-verify")
-    public Result<WxLoginCheckVO> wxPhoneVerify(@RequestBody WxPhoneLoginDTO dto) {
-        log.info("【第二步】手机号授权验证,loginCode: {}", dto.getLoginCode());
-        
-        if (dto.getLoginCode() == null || dto.getLoginCode().isEmpty()) {
-            return Result.error("登录code不能为空");
-        }
-        
-        if (dto.getEncryptedData() == null || dto.getIv() == null) {
-            return Result.error("手机号加密数据不能为空");
-        }
-        
-        try {
-            WxLoginCheckVO result = authService.wxPhoneCheck(dto);
-            return Result.success(result);
-        } catch (Exception e) {
-            log.error("手机号授权验证失败", e);
-            return Result.error("验证失败:" + e.getMessage());
-        }
-    }
-    
-    /**
-     * 第三步:完善用户信息(新用户)
-     * 接口路径:POST /v1/auth/wx/register
-     * 
-     * @param dto 包含 openid, unionid, phoneNumber, nickname, avatarUrl
-     * @return 返回 { token }
-     */
-    @PostMapping("/register")
-    public Result<LoginVO> wxRegister(@RequestBody WxCompleteUserInfoDTO dto) {
-        log.info("【第三步】完善用户信息,openid: {}, phone: {}, nickname: {}", 
-                dto.getOpenid(), dto.getPhoneNumber(), dto.getNickname());
-        
-        if (dto.getOpenid() == null || dto.getOpenid().isEmpty()) {
-            return Result.error("openid不能为空");
-        }
-        
-        if (dto.getPhoneNumber() == null || dto.getPhoneNumber().isEmpty()) {
-            return Result.error("手机号不能为空");
-        }
-        
-        if (dto.getNickname() == null || dto.getNickname().isEmpty()) {
-            return Result.error("昵称不能为空");
-        }
-        
-        try {
-            LoginVO result = authService.wxCompleteUserInfo(dto);
-            return Result.success(result);
-        } catch (Exception e) {
-            log.error("完善用户信息失败", e);
-            return Result.error("注册失败:" + e.getMessage());
-        }
-    }
-
-}

+ 123 - 0
src/main/java/com/yingpai/gupiao/controller/H5AuthController.java

@@ -0,0 +1,123 @@
+package com.yingpai.gupiao.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.yingpai.gupiao.domain.dto.H5PhoneLoginDTO;
+import com.yingpai.gupiao.domain.vo.LoginVO;
+import com.yingpai.gupiao.domain.vo.Result;
+import com.yingpai.gupiao.domain.vo.WxH5UserInfoVO;
+import com.yingpai.gupiao.service.H5AuthService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * H5公众号认证控制器
+ * 处理微信公众号网页授权登录
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/v1/auth/h5")
+public class H5AuthController {
+
+    private final H5AuthService h5AuthService;
+
+    /**
+     * 获取微信授权URL
+     * 前端调用此接口获取授权链接,然后重定向到微信授权页面
+     *
+     * @param redirectUrl 授权后的回调地址
+     * @return 微信授权URL
+     */
+    @GetMapping("/auth-url")
+    public Result<String> getAuthUrl(@RequestParam("redirectUrl") String redirectUrl) {
+        log.info("获取微信授权URL,redirectUrl: {}", redirectUrl);
+
+        if (StrUtil.isBlank(redirectUrl)) {
+            return Result.error("回调地址不能为空");
+        }
+
+        try {
+            String authUrl = h5AuthService.getAuthUrl(redirectUrl);
+            return Result.success(authUrl);
+        } catch (Exception e) {
+            log.error("获取授权URL失败", e);
+            return Result.error("获取授权URL失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 微信授权回调处理
+     * 用户授权后,微信会重定向到此接口,携带code参数
+     *
+     * @param code 微信授权code
+     * @return 用户信息(openid、昵称、头像等)
+     */
+    @GetMapping("/callback")
+    public Result<WxH5UserInfoVO> authCallback(@RequestParam("code") String code) {
+        log.info("微信授权回调,code: {}", code);
+
+        if (StrUtil.isBlank(code)) {
+            return Result.error("授权code不能为空");
+        }
+
+        try {
+            WxH5UserInfoVO userInfo = h5AuthService.getUserInfoByCode(code);
+            return Result.success(userInfo);
+        } catch (Exception e) {
+            log.error("授权回调处理失败", e);
+            return Result.error("授权失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 手机号登录/注册
+     * 用户输入手机号和验证码完成登录或注册
+     *
+     * @param dto 包含 openid、手机号、验证码、昵称、头像
+     * @return token
+     */
+    @PostMapping("/phone-login")
+    public Result<LoginVO> phoneLogin(@RequestBody H5PhoneLoginDTO dto) {
+        log.info("手机号登录,openid: {}", dto.getOpenid());
+
+        if (StrUtil.isBlank(dto.getOpenid())) {
+            return Result.error("openid不能为空");
+        }
+
+        if (StrUtil.isBlank(dto.getPhone())) {
+            return Result.error("手机号不能为空");
+        }
+
+        try {
+            LoginVO result = h5AuthService.phoneLogin(dto);
+            return Result.success(result);
+        } catch (Exception e) {
+            log.error("手机号登录失败", e);
+            return Result.error("登录失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 发送短信验证码
+     *
+     * @param phone 手机号
+     * @return 成功/失败
+     */
+    @PostMapping("/send-sms")
+    public Result<Void> sendSms(@RequestParam("phone") String phone) {
+        log.info("发送短信验证码,phone: {}", phone);
+
+        if (StrUtil.isBlank(phone)) {
+            return Result.error("手机号不能为空");
+        }
+
+        try {
+            h5AuthService.sendSmsCode(phone);
+            return Result.success(null);
+        } catch (Exception e) {
+            log.error("发送短信验证码失败", e);
+            return Result.error("发送失败:" + e.getMessage());
+        }
+    }
+}

+ 34 - 0
src/main/java/com/yingpai/gupiao/domain/dto/H5PhoneLoginDTO.java

@@ -0,0 +1,34 @@
+package com.yingpai.gupiao.domain.dto;
+
+import lombok.Data;
+
+/**
+ * H5手机号登录DTO
+ */
+@Data
+public class H5PhoneLoginDTO {
+    /**
+     * 微信openid
+     */
+    private String openid;
+
+    /**
+     * 手机号
+     */
+    private String phone;
+
+    /**
+     * 昵称(新用户注册时需要)
+     */
+    private String nickname;
+
+    /**
+     * 头像URL(新用户注册时需要)
+     */
+    private String avatarUrl;
+
+    /**
+     * unionid(可选)
+     */
+    private String unionid;
+}

+ 61 - 0
src/main/java/com/yingpai/gupiao/domain/vo/WxH5UserInfoVO.java

@@ -0,0 +1,61 @@
+package com.yingpai.gupiao.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 微信H5用户信息VO
+ */
+@Data
+@Builder
+public class WxH5UserInfoVO {
+    /**
+     * 微信openid
+     */
+    private String openid;
+
+    /**
+     * 微信unionid
+     */
+    private String unionid;
+
+    /**
+     * 昵称
+     */
+    private String nickname;
+
+    /**
+     * 头像URL
+     */
+    private String avatarUrl;
+
+    /**
+     * 性别(0-未知,1-男,2-女)
+     */
+    private Integer sex;
+
+    /**
+     * 国家
+     */
+    private String country;
+
+    /**
+     * 省份
+     */
+    private String province;
+
+    /**
+     * 城市
+     */
+    private String city;
+
+    /**
+     * 是否已注册(true-老用户,false-新用户)
+     */
+    private Boolean isRegistered;
+
+    /**
+     * token(老用户直接返回token)
+     */
+    private String token;
+}

+ 10 - 7
src/main/java/com/yingpai/gupiao/domain/vo/WxPayVO.java

@@ -14,25 +14,28 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class WxPayVO {
-    
+
     /** 订单号 */
     private String orderNo;
-    
+
+    /** 微信AppID(H5支付需要) */
+    private String appId;
+
     /** 时间戳 */
     private String timeStamp;
-    
+
     /** 随机字符串 */
     private String nonceStr;
-    
+
     /** 预支付交易会话标识(prepay_id=xxx) */
     private String packageValue;
-    
+
     /** 签名类型 */
     private String signType;
-    
+
     /** 签名 */
     private String paySign;
-    
+
     /** 支付金额(单位:分) */
     @JsonProperty("total_fee")
     private Integer totalFee;

+ 0 - 41
src/main/java/com/yingpai/gupiao/service/AuthService.java

@@ -1,41 +0,0 @@
-package com.yingpai.gupiao.service;
-
-import com.yingpai.gupiao.domain.dto.WxCompleteUserInfoDTO;
-import com.yingpai.gupiao.domain.dto.WxPhoneLoginDTO;
-import com.yingpai.gupiao.domain.vo.LoginVO;
-import com.yingpai.gupiao.domain.vo.WxLoginCheckVO;
-
-/**
- * 认证服务接口
- * 提供用户登录相关功能
- * 
- * 完整登录流程:
- * 1. wxSilentLogin - 检查是否为老用户
- * 2. wxPhoneCheck - 验证手机号
- * 3. wxCompleteUserInfo - 完善用户信息
- */
-public interface AuthService {
-    
-    /**
-     * 第一步:微信静默登录(检查是否为老用户)
-     * @param loginCode 微信登录code
-     * @return 老用户返回token,新用户返回isSign=false
-     */
-    WxLoginCheckVO wxSilentLogin(String loginCode);
-    
-    /**
-     * 第二步:手机号授权验证
-     * @param dto 包含loginCode和加密phone
-     * @return 已注册返回token,未注册返回用户信息
-     */
-    WxLoginCheckVO wxPhoneCheck(WxPhoneLoginDTO dto);
-    
-    /**
-     * 第三步:完善用户信息(首次登录)
-     * @param dto 包含完整用户信息
-     * @return 返回token
-     */
-    LoginVO wxCompleteUserInfo(WxCompleteUserInfoDTO dto);
-}
-
-

+ 38 - 0
src/main/java/com/yingpai/gupiao/service/H5AuthService.java

@@ -0,0 +1,38 @@
+package com.yingpai.gupiao.service;
+
+import com.yingpai.gupiao.domain.dto.H5PhoneLoginDTO;
+import com.yingpai.gupiao.domain.vo.LoginVO;
+import com.yingpai.gupiao.domain.vo.WxH5UserInfoVO;
+
+/**
+ * H5公众号认证服务接口
+ */
+public interface H5AuthService {
+
+    /**
+     * 获取微信授权URL
+     * @param redirectUrl 授权后的回调地址
+     * @return 微信授权URL
+     */
+    String getAuthUrl(String redirectUrl);
+
+    /**
+     * 通过code获取用户信息
+     * @param code 微信授权code
+     * @return 用户信息
+     */
+    WxH5UserInfoVO getUserInfoByCode(String code) throws Exception;
+
+    /**
+     * 手机号登录/注册
+     * @param dto 登录信息
+     * @return token
+     */
+    LoginVO phoneLogin(H5PhoneLoginDTO dto) throws Exception;
+
+    /**
+     * 发送短信验证码
+     * @param phone 手机号
+     */
+    void sendSmsCode(String phone) throws Exception;
+}

+ 0 - 247
src/main/java/com/yingpai/gupiao/service/impl/AuthServiceImpl.java

@@ -1,247 +0,0 @@
-package com.yingpai.gupiao.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.yingpai.gupiao.domain.dto.WxCompleteUserInfoDTO;
-import com.yingpai.gupiao.domain.dto.WxLoginResponse;
-import com.yingpai.gupiao.domain.dto.WxPhoneLoginDTO;
-import com.yingpai.gupiao.domain.po.User;
-import com.yingpai.gupiao.domain.vo.LoginVO;
-import com.yingpai.gupiao.domain.vo.WxLoginCheckVO;
-import com.yingpai.gupiao.mapper.UserMapper;
-import com.yingpai.gupiao.service.AuthService;
-import com.yingpai.gupiao.util.JwtUtil;
-import com.yingpai.gupiao.util.WxApiUtil;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.time.LocalDateTime;
-
-/**
- * 认证服务实现类
- * 用于处理微信小程序登录认证
- * 
- * 完整登录流程:
- * 1. 静默登录:wx.login() -> POST /v1/auth/wx/silent-login(老用户直接返回token)
- * 2. 新用户获取头像昵称:前端弹窗选择头像和输入昵称
- * 3. 手机号授权:getPhoneNumber -> POST /v1/auth/wx/phone-verify(已注册用户返回token,未注册返回openid等信息)
- * 4. 完善用户信息:头像+昵称+手机号 -> POST /v1/auth/wx/register(创建新用户,返回token)
- * 5. 获取用户信息:token -> GET /v1/user/info
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class AuthServiceImpl implements AuthService {
-    
-    private final UserMapper userMapper;
-    private final JwtUtil jwtUtil;
-    private final WxApiUtil wxApiUtil;
-    
-    /**
-     * 第一步:微信静默登录
-     * 小程序调用 wx.login() 获取 code,发送到后端检查是否为老用户
-     * 
-     * @param loginCode 微信登录code
-     * @return 老用户返回 isSign=true + token,新用户返回 isSign=false
-     */
-    @Override
-    public WxLoginCheckVO wxSilentLogin(String loginCode) {
-        try {
-            log.info("【第一步】微信静默登录,loginCode: {}", loginCode);
-            
-            // 1. 调用微信API获取openid和unionid
-            WxLoginResponse wxResponse = wxApiUtil.getOpenidByCode(loginCode);
-            String openid = wxResponse.getOpenid();
-            String unionid = wxResponse.getUnionid();
-            
-            log.info("获取微信信息成功,openid: {}, unionid: {}", openid, unionid);
-            
-            // 2. 根据openid查询用户
-            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
-            wrapper.eq(User::getOpenid, openid);
-            User user = userMapper.selectOne(wrapper);
-            
-            // 3. 判断用户是否存在
-            if (user == null) {
-                // 新用户,返回 isSign=false
-                log.info("新用户,需要授权手机号");
-                return WxLoginCheckVO.builder()
-                        .isSign("false")
-                        .build();
-            }
-            
-            // 4. 检查用户状态
-            if (user.getStatus() != null && user.getStatus() == 1) {
-                // 账号被禁用
-                log.warn("账号被禁用,userId: {}", user.getId());
-                return WxLoginCheckVO.builder()
-                        .code(103)
-                        .message("账号已被禁用")
-                        .build();
-            }
-            
-            // 5. 老用户,生成token并返回
-            String token = jwtUtil.generateToken(user.getId());
-            log.info("老用户登录成功,userId: {}, token已生成", user.getId());
-            
-            return WxLoginCheckVO.builder()
-                    .isSign("true")
-                    .token(token)
-                    .build();
-                    
-        } catch (Exception e) {
-            log.error("微信静默登录失败", e);
-            throw new RuntimeException("微信静默登录失败: " + e.getMessage());
-        }
-    }
-    
-    /**
-     * 第二步(新用户):手机号授权验证
-     * 小程序调用 getPhoneNumber 获取加密手机号,发送到后端解密验证
-     * 
-     * @param dto 包含 loginCode、encryptedData、iv
-     * @return 已注册用户返回 isSign=true + token,未注册返回 isSign=false + openid/unionid/phoneNumber
-     */
-    @Override
-    public WxLoginCheckVO wxPhoneCheck(WxPhoneLoginDTO dto) {
-        try {
-            log.info("【第二步】手机号授权验证,loginCode: {}", dto.getLoginCode());
-            
-            // 1. 调用微信API获取openid、unionid和session_key
-            WxLoginResponse wxResponse = wxApiUtil.getOpenidByCode(dto.getLoginCode());
-            String openid = wxResponse.getOpenid();
-            String unionid = wxResponse.getUnionid();
-            String sessionKey = wxResponse.getSessionKey();
-            
-            log.info("获取微信信息成功,openid: {}, unionid: {}, sessionKey已获取", openid, unionid);
-            
-            // 2. 解密手机号(使用encryptedData和iv方式)
-            String phone = null;
-            
-            if (dto.getEncryptedData() != null && dto.getIv() != null && sessionKey != null) {
-                // 使用本地解密方式
-                log.info("使用encryptedData解密手机号");
-                phone = wxApiUtil.decryptPhoneNumber(dto.getEncryptedData(), sessionKey, dto.getIv());
-                log.info("解密手机号成功: {}", phone);
-            } else {
-                log.error("缺少解密参数:encryptedData={}, iv={}, sessionKey={}", 
-                    dto.getEncryptedData() != null, dto.getIv() != null, sessionKey != null);
-                throw new RuntimeException("缺少手机号解密参数");
-            }
-            
-            // 3. 根据手机号查询用户
-            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
-            wrapper.eq(User::getPhone, phone);
-            User user = userMapper.selectOne(wrapper);
-            
-            // 4. 判断用户是否存在
-            if (user == null) {
-                // 未注册,返回用户信息供前端完善资料
-                log.info("未注册用户,需要完善信息");
-                return WxLoginCheckVO.builder()
-                        .isSign("false")
-                        .openid(openid)
-                        .unionid(unionid)
-                        .phoneNumber(phone)
-                        .build();
-            }
-            
-            // 5. 已注册用户,更新openid和unionid(如果没有)
-            boolean needUpdate = false;
-            
-            if (user.getOpenid() == null || user.getOpenid().isEmpty()) {
-                user.setOpenid(openid);
-                needUpdate = true;
-            }
-            
-            if (unionid != null && (user.getUnionid() == null || user.getUnionid().isEmpty())) {
-                user.setUnionid(unionid);
-                needUpdate = true;
-            }
-            
-            if (needUpdate) {
-                user.setUpdateTime(LocalDateTime.now());
-                userMapper.updateById(user);
-                log.info("更新用户微信信息,userId: {}", user.getId());
-            }
-            
-            // 6. 生成token并返回
-            String token = jwtUtil.generateToken(user.getId());
-            log.info("已注册用户登录成功,userId: {}, token已生成", user.getId());
-            
-            return WxLoginCheckVO.builder()
-                    .isSign("true")
-                    .token(token)
-                    .build();
-                    
-        } catch (Exception e) {
-            log.error("手机号授权验证失败", e);
-            throw new RuntimeException("手机号授权验证失败: " + e.getMessage());
-        }
-    }
-    
-    /**
-     * 第三步(新用户):完善用户信息并注册
-     * 小程序提交完整的用户信息(头像+昵称+手机号+openid等)完成注册
-     * 
-     * @param dto 包含 openid、unionid、phoneNumber、nickname、avatarUrl
-     * @return 返回 token
-     */
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public LoginVO wxCompleteUserInfo(WxCompleteUserInfoDTO dto) {
-        try {
-            log.info("【第三步】完善用户信息,openid: {}, phone: {}, nickname: {}", 
-                    dto.getOpenid(), dto.getPhoneNumber(), dto.getNickname());
-            
-            // 1. 检查手机号是否已被注册
-            LambdaQueryWrapper<User> phoneWrapper = new LambdaQueryWrapper<>();
-            phoneWrapper.eq(User::getPhone, dto.getPhoneNumber());
-            User existUser = userMapper.selectOne(phoneWrapper);
-            
-            if (existUser != null) {
-                log.warn("手机号已被注册,phone: {}, userId: {}", dto.getPhoneNumber(), existUser.getId());
-                throw new RuntimeException("该手机号已被注册");
-            }
-            
-            // 2. 检查openid是否已存在
-            LambdaQueryWrapper<User> openidWrapper = new LambdaQueryWrapper<>();
-            openidWrapper.eq(User::getOpenid, dto.getOpenid());
-            existUser = userMapper.selectOne(openidWrapper);
-            
-            if (existUser != null) {
-                log.warn("openid已存在,openid: {}, userId: {}", dto.getOpenid(), existUser.getId());
-                throw new RuntimeException("该微信账号已注册");
-            }
-            
-            // 3. 创建新用户
-            User newUser = User.builder()
-                    .openid(dto.getOpenid())
-                    .unionid(dto.getUnionid())
-                    .phone(dto.getPhoneNumber())
-                    .nickname(dto.getNickname())
-                    .avatar(dto.getAvatarUrl())
-                    .status(0)
-                    .createTime(LocalDateTime.now())
-                    .updateTime(LocalDateTime.now())
-                    .build();
-            
-            userMapper.insert(newUser);
-            log.info("创建新用户成功,userId: {}, openid: {}, phone: {}, nickname: {}", 
-                    newUser.getId(), dto.getOpenid(), dto.getPhoneNumber(), dto.getNickname());
-            
-            // 4. 生成token
-            String token = jwtUtil.generateToken(newUser.getId());
-            
-            // 5. 返回token
-            return LoginVO.builder()
-                    .token(token)
-                    .build();
-            
-        } catch (Exception e) {
-            log.error("完善用户信息失败", e);
-            throw new RuntimeException("注册失败: " + e.getMessage());
-        }
-    }
-}

+ 253 - 0
src/main/java/com/yingpai/gupiao/service/impl/H5AuthServiceImpl.java

@@ -0,0 +1,253 @@
+package com.yingpai.gupiao.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yingpai.gupiao.config.WxConfig;
+import com.yingpai.gupiao.domain.dto.H5PhoneLoginDTO;
+import com.yingpai.gupiao.domain.po.User;
+import com.yingpai.gupiao.domain.vo.LoginVO;
+import com.yingpai.gupiao.domain.vo.WxH5UserInfoVO;
+import com.yingpai.gupiao.mapper.UserMapper;
+import com.yingpai.gupiao.service.H5AuthService;
+import com.yingpai.gupiao.util.JwtUtil;
+import com.yingpai.gupiao.util.WxApiUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * H5公众号认证服务实现
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class H5AuthServiceImpl implements H5AuthService {
+
+    private final WxConfig wxConfig;
+    private final UserMapper userMapper;
+    private final JwtUtil jwtUtil;
+    private final RestTemplate restTemplate = new RestTemplate();
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    // 微信OAuth2.0授权URL
+    private static final String WX_OAUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
+    // 获取access_token的URL
+    private static final String WX_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
+    // 获取用户信息的URL
+    private static final String WX_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";
+
+    @Override
+    public String getAuthUrl(String redirectUrl) {
+        try {
+            // 构建微信授权URL
+            // scope=snsapi_userinfo 可以获取用户基本信息(昵称、头像等)
+            // scope=snsapi_base 只能获openid,无法获取用户信息
+            String encodedRedirectUrl = URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8);
+            String state = UUID.randomUUID().toString().replace("-", "");
+            String authUrl = String.format(
+                "%s?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect",
+                WX_OAUTH_URL,
+                wxConfig.getAppid(),
+                encodedRedirectUrl,
+                state
+            );
+
+            log.info("生成微信授权URL: {}", authUrl);
+            return authUrl;
+        } catch (Exception e) {
+            log.error("生成授权URL失败", e);
+            throw new RuntimeException("生成授权URL失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public WxH5UserInfoVO getUserInfoByCode(String code) throws Exception {
+        try {
+            log.info("通过code获取用户信息,code: {}", code);
+
+            // 1. 通过code获取access_token和openid
+            String tokenUrl = String.format(
+                "%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
+                WX_ACCESS_TOKEN_URL,
+                wxConfig.getAppid(),
+                wxConfig.getSecret(),
+                code
+            );
+
+            String tokenResponse = restTemplate.getForObject(tokenUrl, String.class);
+            log.info("获取access_token响应: {}", tokenResponse);
+
+            @SuppressWarnings("unchecked")
+            Map<String, Object> tokenMap = objectMapper.readValue(tokenResponse, Map.class);
+
+            if (tokenMap.containsKey("errcode")) {
+                Integer errcode = (Integer) tokenMap.get("errcode");
+                String errmsg = (String) tokenMap.get("errmsg");
+                log.error("获取access_token失败,errcode: {}, errmsg: {}", errcode, errmsg);
+                throw new RuntimeException("获取access_token失败: " + errmsg);
+            }
+
+            String accessToken = (String) tokenMap.get("access_token");
+            String openid = (String) tokenMap.get("openid");
+            String unionid = (String) tokenMap.get("unionid");
+
+            log.info("获取access_token成功,openid: {}, unionid: {}", openid, unionid);
+
+            // 2. 通过access_token和openid获取用户信息
+            String userInfoUrl = String.format(
+                "%s?access_token=%s&openid=%s&lang=zh_CN",
+                WX_USER_INFO_URL,
+                accessToken,
+                openid
+            );
+
+            String userInfoResponse = restTemplate.getForObject(userInfoUrl, String.class);
+            log.info("获取用户信息响应: {}", userInfoResponse);
+
+            @SuppressWarnings("unchecked")
+            Map<String, Object> userInfoMap = objectMapper.readValue(userInfoResponse, Map.class);
+
+            if (userInfoMap.containsKey("errcode")) {
+                Integer errcode = (Integer) userInfoMap.get("errcode");
+                String errmsg = (String) userInfoMap.get("errmsg");
+                log.error("获取用户信息失败,errcode: {}, errmsg: {}", errcode, errmsg);
+                throw new RuntimeException("获取用户信息失败: " + errmsg);
+            }
+
+            String nickname = (String) userInfoMap.get("nickname");
+            String avatarUrl = (String) userInfoMap.get("headimgurl");
+            Integer sex = (Integer) userInfoMap.get("sex");
+            String country = (String) userInfoMap.get("country");
+            String province = (String) userInfoMap.get("province");
+            String city = (String) userInfoMap.get("city");
+
+            log.info("获取用户信息成功,nickname: {}, openid: {}", nickname, openid);
+
+            // 3. 检查用户是否已注册
+            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(User::getOpenid, openid);
+            User user = userMapper.selectOne(wrapper);
+
+            boolean isRegistered = user != null;
+            String token = null;
+
+            if (isRegistered) {
+                // 老用户,直接生成token
+                token = jwtUtil.generateToken(user.getId());
+                log.info("老用户登录,userId: {}, token已生成", user.getId());
+            }
+
+            return WxH5UserInfoVO.builder()
+                    .openid(openid)
+                    .unionid(unionid)
+                    .nickname(nickname)
+                    .avatarUrl(avatarUrl)
+                    .sex(sex)
+                    .country(country)
+                    .province(province)
+                    .city(city)
+                    .isRegistered(isRegistered)
+                    .token(token)
+                    .build();
+
+        } catch (Exception e) {
+            log.error("获取用户信息失败", e);
+            throw new Exception("获取用户信息失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public LoginVO phoneLogin(H5PhoneLoginDTO dto) throws Exception {
+        try {
+            log.info("手机号登录,openid: {}, phone: {}", dto.getOpenid(), dto.getPhone());
+
+            // 直接使用用户输入的手机号,不需要验证码验证
+            String phone = dto.getPhone();
+
+            if (StrUtil.isBlank(phone)) {
+                throw new RuntimeException("手机号不能为空");
+            }
+
+            // 简单的手机号格式验证
+            if (!phone.matches("^1[3-9]\\d{9}$")) {
+                throw new RuntimeException("手机号格式不正确");
+            }
+
+            log.info("使用手机号: {}", phone);
+
+            // 2. 查询用户是否存在
+            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(User::getPhone, phone);
+            User user = userMapper.selectOne(wrapper);
+
+            if (user == null) {
+                // 新用户,创建账号
+                if (StrUtil.isBlank(dto.getNickname())) {
+                    throw new RuntimeException("昵称不能为空");
+                }
+
+                user = User.builder()
+                        .openid(dto.getOpenid())
+                        .unionid(dto.getUnionid())
+                        .phone(phone)
+                        .nickname(dto.getNickname())
+                        .avatar(dto.getAvatarUrl())
+                        .status(0)
+                        .createTime(LocalDateTime.now())
+                        .updateTime(LocalDateTime.now())
+                        .build();
+
+                userMapper.insert(user);
+                log.info("创建新用户成功,userId: {}, phone: {}", user.getId(), phone);
+            } else {
+                // 老用户,更新openid和unionid
+                boolean needUpdate = false;
+
+                if (StrUtil.isBlank(user.getOpenid())) {
+                    user.setOpenid(dto.getOpenid());
+                    needUpdate = true;
+                }
+
+                if (StrUtil.isNotBlank(dto.getUnionid()) && StrUtil.isBlank(user.getUnionid())) {
+                    user.setUnionid(dto.getUnionid());
+                    needUpdate = true;
+                }
+
+                if (needUpdate) {
+                    user.setUpdateTime(LocalDateTime.now());
+                    userMapper.updateById(user);
+                    log.info("更新用户微信信息,userId: {}", user.getId());
+                }
+
+                log.info("老用户登录,userId: {}", user.getId());
+            }
+
+            // 3. 生成token
+            String token = jwtUtil.generateToken(user.getId());
+
+            return LoginVO.builder()
+                    .token(token)
+                    .build();
+
+        } catch (Exception e) {
+            log.error("手机号登录失败", e);
+            throw new Exception("登录失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public void sendSmsCode(String phone) throws Exception {
+        // 不再需要短信验证码功能,使用微信手机号快速验证
+        throw new UnsupportedOperationException("请使用微信手机号快速验证");
+    }
+}

+ 71 - 1
src/main/java/com/yingpai/gupiao/service/impl/UserStockServiceImpl.java

@@ -1,6 +1,8 @@
 package com.yingpai.gupiao.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.yingpai.gupiao.domain.dto.AddUserStockDTO;
 import com.yingpai.gupiao.domain.po.UserStock;
 import com.yingpai.gupiao.domain.vo.UserStockVO;
@@ -38,11 +40,14 @@ public class UserStockServiceImpl implements UserStockService {
 
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
     private static final String BASE_URL = "http://qt.gtimg.cn/q=";
+    private static final String TREND_URL = "https://web.ifzq.gtimg.cn/appstock/app/minute/query";
 
     private final HttpClient httpClient = HttpClient.newBuilder()
             .connectTimeout(Duration.ofSeconds(3))
             .build();
 
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
     @Override
     public List<UserStockVO> getUserStocks(Long userId) {
         log.info("[查询股票] userId={}", userId);
@@ -93,6 +98,10 @@ public class UserStockServiceImpl implements UserStockService {
                 }
             }
 
+            // 获取分时数据
+            List<BigDecimal> trendData = fetchTrendData(stock.getStockCode());
+            builder.trendData(trendData);
+
             result.add(builder.build());
         }
 
@@ -244,7 +253,68 @@ public class UserStockServiceImpl implements UserStockService {
         wrapper.eq(UserStock::getUserId, userId)
                .eq(UserStock::getStockCode, stockCode)
                .eq(UserStock::getPoolType, poolType);
-        
+
         return userStockMapper.selectCount(wrapper) > 0;
     }
+
+    /**
+     * 获取股票分时数据
+     */
+    private List<BigDecimal> fetchTrendData(String stockCode) {
+        List<BigDecimal> trendData = new ArrayList<>();
+
+        try {
+            String marketPrefix = StockUtils.getMarketPrefix(stockCode);
+            if (marketPrefix == null) {
+                return trendData;
+            }
+
+            String fullUrl = TREND_URL + "?code=" + marketPrefix + stockCode;
+
+            HttpRequest request = HttpRequest.newBuilder()
+                    .uri(URI.create(fullUrl))
+                    .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(marketPrefix + stockCode).path("data").path("data");
+
+                if (data != null && data.isArray()) {
+                    List<BigDecimal> allPrices = new ArrayList<>();
+                    for (JsonNode item : data) {
+                        String itemStr = item.asText();
+                        String[] parts = itemStr.split(" ");
+                        if (parts.length >= 2) {
+                            try {
+                                BigDecimal price = new BigDecimal(parts[1]);
+                                allPrices.add(price);
+                            } catch (NumberFormatException e) {
+                                log.debug("[趋势数据] 解析价格失败: {}", parts[1]);
+                            }
+                        }
+                    }
+
+                    int targetPoints = 30;
+                    if (allPrices.size() <= targetPoints) {
+                        trendData = allPrices;
+                    } else {
+                        double step = (double) (allPrices.size() - 1) / (targetPoints - 1);
+                        for (int i = 0; i < targetPoints; i++) {
+                            int index = (int) Math.round(i * step);
+                            if (index < allPrices.size()) {
+                                trendData.add(allPrices.get(index));
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("[趋势数据] 获取失败: {}", e.getMessage());
+        }
+
+        return trendData;
+    }
 }

+ 2 - 1
src/main/java/com/yingpai/gupiao/service/impl/WxPayServiceImpl.java

@@ -168,9 +168,10 @@ public class WxPayServiceImpl implements WxPayService {
         PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(request);
         
         log.info("预支付成功,orderNo: {}", orderNo);
-        
+
         return WxPayVO.builder()
                 .orderNo(orderNo)
+                .appId(wxConfig.getAppid())
                 .timeStamp(response.getTimeStamp())
                 .nonceStr(response.getNonceStr())
                 .packageValue(response.getPackageVal())

+ 86 - 10
src/main/java/com/yingpai/gupiao/util/WxApiUtil.java

@@ -154,10 +154,86 @@ public class WxApiUtil {
         }
     }
     
+    /**
+     * 通过code获取手机号(新版方式,使用微信接口)
+     * 适用于小程序和公众号H5
+     * 需要开通"手机号快速验证组件"权限
+     *
+     * @param code 手机号授权code
+     * @return 手机号
+     * @throws Exception 调用失败时抛出异常
+     */
+    public String getPhoneNumberByCode(String code) throws Exception {
+        try {
+            log.info("通过code获取手机号");
+
+            // 1. 获取access_token
+            String accessToken = getAccessToken();
+
+            // 2. 构建请求URL
+            String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
+
+            // 3. 构建请求体
+            Map<String, String> requestBody = new java.util.HashMap<>();
+            requestBody.put("code", code);
+            String requestJson = objectMapper.writeValueAsString(requestBody);
+
+            log.info("调用获取手机号接口,code: {}", code);
+
+            // 4. 发送POST请求
+            org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
+            headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
+            org.springframework.http.HttpEntity<String> entity = new org.springframework.http.HttpEntity<>(requestJson, headers);
+
+            String response = restTemplate.postForObject(url, entity, String.class);
+            log.info("获取手机号响应: {}", response);
+
+            // 5. 解析响应
+            @SuppressWarnings("unchecked")
+            Map<String, Object> result = objectMapper.readValue(response, Map.class);
+
+            // 检查是否有错误
+            if (result.containsKey("errcode")) {
+                Integer errcode = (Integer) result.get("errcode");
+                if (errcode != 0) {
+                    String errmsg = (String) result.get("errmsg");
+                    log.error("获取手机号失败,errcode: {}, errmsg: {}", errcode, errmsg);
+                    throw new RuntimeException("获取手机号失败: " + errmsg);
+                }
+            }
+
+            // 6. 提取手机号
+            @SuppressWarnings("unchecked")
+            Map<String, Object> phoneInfo = (Map<String, Object>) result.get("phone_info");
+            if (phoneInfo == null) {
+                log.error("响应中未找到phone_info");
+                throw new RuntimeException("获取手机号失败: 响应格式错误");
+            }
+
+            String phoneNumber = (String) phoneInfo.get("purePhoneNumber");
+            if (phoneNumber == null || phoneNumber.isEmpty()) {
+                // 尝试获取phoneNumber字段
+                phoneNumber = (String) phoneInfo.get("phoneNumber");
+            }
+
+            if (phoneNumber == null || phoneNumber.isEmpty()) {
+                log.error("phone_info中未找到手机号");
+                throw new RuntimeException("获取手机号失败: 未返回手机号");
+            }
+
+            log.info("成功获取手机号: {}", phoneNumber);
+            return phoneNumber;
+
+        } catch (Exception e) {
+            log.error("获取手机号失败", e);
+            throw new Exception("获取手机号失败: " + e.getMessage());
+        }
+    }
+
     /**
      * 解密手机号(旧版方式,使用encryptedData和iv)
      * 不需要开通"手机号快速验证组件"权限
-     * 
+     *
      * @param encryptedData 加密数据
      * @param sessionKey 会话密钥
      * @param iv 加密算法的初始向量
@@ -167,37 +243,37 @@ public class WxApiUtil {
     public String decryptPhoneNumber(String encryptedData, String sessionKey, String iv) throws Exception {
         try {
             log.info("开始解密手机号");
-            
+
             // Base64解码
             byte[] encryptedDataBytes = java.util.Base64.getDecoder().decode(encryptedData);
             byte[] sessionKeyBytes = java.util.Base64.getDecoder().decode(sessionKey);
             byte[] ivBytes = java.util.Base64.getDecoder().decode(iv);
-            
+
             // AES解密
             javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(sessionKeyBytes, "AES");
             javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
             javax.crypto.spec.IvParameterSpec ivSpec = new javax.crypto.spec.IvParameterSpec(ivBytes);
             cipher.init(javax.crypto.Cipher.DECRYPT_MODE, keySpec, ivSpec);
-            
+
             byte[] decryptedBytes = cipher.doFinal(encryptedDataBytes);
             String decryptedData = new String(decryptedBytes, "UTF-8");
-            
+
             log.info("解密成功");
-            
+
             // 解析JSON获取手机号
             @SuppressWarnings("unchecked")
             Map<String, Object> dataMap = objectMapper.readValue(decryptedData, Map.class);
-            
+
             String phoneNumber = (String) dataMap.get("purePhoneNumber");
-            
+
             if (phoneNumber == null || phoneNumber.isEmpty()) {
                 log.error("解密数据中未找到手机号");
                 throw new RuntimeException("解密数据中未找到手机号");
             }
-            
+
             log.info("成功提取手机号: {}", phoneNumber);
             return phoneNumber;
-            
+
         } catch (Exception e) {
             log.error("解密手机号失败", e);
             throw new Exception("解密手机号失败: " + e.getMessage());

+ 6 - 6
src/main/resources/application.yml

@@ -3,8 +3,8 @@ server:
 
 # 微信配置
 wx:
-  appid: wx9d7e6e3592830447
-  secret: f79f4bf879df37aff9e12fc81f3ff901
+  appid: wx3a2e1ce6a8cf342d
+  secret: cdd169a5575237cee52725529411e6a3
 
 # 文件存储配置
 file:
@@ -18,10 +18,10 @@ spring:
   
   datasource:
     url: jdbc:mysql://localhost:3306/ry_vue_5.x?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
-    username: root
-#    username: gupiao
-    password: 121380
-#    password: zQpdD3AmAPNCTFMyYARWMXnHSAbeHKEDNDHDYDZrFTAYGXGNVs
+#    username: root
+    username: gupiao
+#    password: 121380
+    password: zQpdD3AmAPNCTFMyYARWMXnHSAbeHKEDNDHDYDZrFTAYGXGNVs
     driver-class-name: com.mysql.cj.jdbc.Driver
 
   servlet:

+ 2 - 27
src/main/resources/ry_vue_5.x.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80042 (8.0.42)
  File Encoding         : 65001
 
- Date: 09/02/2026 13:34:44
+ Date: 10/02/2026 16:34:06
 */
 
 SET NAMES utf8mb4;
@@ -5153,7 +5153,7 @@ CREATE TABLE `stock_pool`  (
   `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   PRIMARY KEY (`id`) USING BTREE,
-  UNIQUE INDEX `uk_stock_pool_type`(`stock_code` ASC, `pool_type` ASC, `status` ASC) USING BTREE,
+  UNIQUE INDEX `uk_stock_pool_type`(`stock_code` ASC, `pool_type` ASC) USING BTREE,
   INDEX `idx_pool_type`(`pool_type` ASC) USING BTREE,
   INDEX `idx_add_date`(`add_date` ASC) USING BTREE,
   INDEX `idx_stock_code`(`stock_code` ASC) USING BTREE,
@@ -5163,31 +5163,6 @@ CREATE TABLE `stock_pool`  (
 -- ----------------------------
 -- Records of stock_pool
 -- ----------------------------
-INSERT INTO `stock_pool` VALUES (8, '000547', '航天发展', 1, 31.41, '2026-02-03', 1, 1, '2025-12-29 00:42:49', '2026-02-03 10:54:22');
-INSERT INTO `stock_pool` VALUES (10, '002702', '海欣食品', 1, 6.80, '2026-02-03', 0, 1, '2025-12-29 00:42:51', '2026-02-03 15:08:39');
-INSERT INTO `stock_pool` VALUES (12, '301232', '飞沃科技', 1, 139.00, '2025-12-29', 0, 1, '2025-12-29 00:43:08', '2025-12-29 00:43:53');
-INSERT INTO `stock_pool` VALUES (14, '301136', '招标股份', 1, 20.25, '2025-12-29', 0, 1, '2025-12-29 09:32:37', '2025-12-29 09:32:37');
-INSERT INTO `stock_pool` VALUES (17, '600343', '航天动力', 1, 37.40, '2026-02-03', 1, 1, '2025-12-29 10:31:47', '2026-02-03 10:54:24');
-INSERT INTO `stock_pool` VALUES (19, '002682', '龙洲股份', 1, 6.99, '2026-02-03', 1, 1, '2025-12-29 10:39:28', '2026-02-03 10:54:25');
-INSERT INTO `stock_pool` VALUES (21, '300102', '乾照光电', 1, 24.97, '2025-12-29', 0, 1, '2025-12-29 15:21:53', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (22, '603696', '安记食品', 1, 18.91, '2026-02-03', 1, 1, '2025-12-29 15:21:53', '2026-02-03 10:54:26');
-INSERT INTO `stock_pool` VALUES (23, '688048', '长光华芯', 1, 132.96, '2025-12-29', 0, 1, '2025-12-29 15:21:54', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (24, '002175', '东方智造', 1, 4.43, '2026-01-04', 0, 19, '2026-01-04 16:27:09', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (50, '002086', '东方海洋', 1, 2.23, '2026-02-02', 0, 24, '2026-02-02 19:42:49', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (51, '001337', '四川黄金', 1, 59.56, '2026-02-02', 0, 24, '2026-02-02 19:58:37', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (52, '601212', '白银有色', 1, 11.07, '2026-02-03', 0, 24, '2026-02-03 09:35:00', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (53, '000506', '招金黄金', 1, 20.79, '2026-02-03', 0, 24, '2026-02-03 09:35:07', '2026-02-03 10:54:14');
-INSERT INTO `stock_pool` VALUES (64, '000682', '东方电子', 1, 13.80, '2026-02-03', 1, 24, '2026-02-03 15:08:37', '2026-02-03 15:08:37');
-INSERT INTO `stock_pool` VALUES (65, '000547', '航天发展', 2, 22.13, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (66, '301232', '飞沃科技', 2, 97.55, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (67, '002702', '海欣食品', 2, 11.06, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (68, '301136', '招标股份', 2, 26.96, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (69, '600343', '航天动力', 2, 32.01, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (70, '300427', '红相股份', 2, 13.38, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (71, '002682', '龙洲股份', 2, 9.54, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (72, '300102', '乾照光电', 2, 26.28, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (73, '603696', '安记食品', 2, 23.56, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
-INSERT INTO `stock_pool` VALUES (74, '688048', '长光华芯', 2, 141.22, '2026-02-03', 1, 1, '2026-02-03 19:42:07', '2026-02-03 19:42:07');
 
 -- ----------------------------
 -- Table structure for stock_pool_history

+ 8 - 0
src/main/resources/掌升科技miniapp.txt

@@ -16,3 +16,11 @@ apiv2:Q2wE4rT6yU8iO0pA1sD3fG5hJ7kL9zXc
 数据库密码:gupiao zQpdD3AmAPNCTFMyYARWMXnHSAbeHKEDNDHDYDZrFTAYGXGNVs
 
 
+服务号 
+AppID
+wx3a2e1ce6a8cf342d
+AppSecret
+cdd169a5575237cee52725529411e6a3
+
+
+