Zhangbw 3 miesięcy temu
rodzic
commit
66e642e6bc

+ 74 - 0
src/main/java/com/yingpai/gupiao/controller/UserPointsController.java

@@ -0,0 +1,74 @@
+package com.yingpai.gupiao.controller;
+
+import com.yingpai.gupiao.domain.po.UserPointsRecord;
+import com.yingpai.gupiao.domain.vo.PointsRecordVO;
+import com.yingpai.gupiao.domain.vo.Result;
+import com.yingpai.gupiao.domain.vo.UserPointsVO;
+import com.yingpai.gupiao.service.DictService;
+import com.yingpai.gupiao.service.UserPointsService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户积分控制器
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/v1/points")
+public class UserPointsController {
+    
+    private final UserPointsService userPointsService;
+    private final DictService dictService;
+    
+    /**
+     * 获取用户积分
+     */
+    @GetMapping("/balance")
+    public Result<UserPointsVO> getBalance(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+        
+        Integer points = userPointsService.getUserPoints(userId);
+        
+        return Result.success(UserPointsVO.builder()
+                .totalPoints(points)
+                .build());
+    }
+    
+    /**
+     * 获取积分记录
+     */
+    @GetMapping("/records")
+    public Result<List<PointsRecordVO>> getRecords(HttpServletRequest request) {
+        Long userId = (Long) request.getAttribute("userId");
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+        
+        List<UserPointsRecord> records = userPointsService.getPointsRecords(userId);
+        
+        List<PointsRecordVO> voList = records.stream().map(record -> 
+            PointsRecordVO.builder()
+                .id(record.getId())
+                .type(record.getType())
+                .amount(record.getAmount())
+                .balance(record.getBalance())
+                .bizType(record.getBizType())
+                .bizTypeName(dictService.getDictLabel(DictService.DICT_POINTS_BIZ_TYPE, record.getBizType()))
+                .remark(record.getRemark())
+                .createTime(record.getCreateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
+                .build()
+        ).collect(Collectors.toList());
+        
+        return Result.success(voList);
+    }
+}

+ 5 - 0
src/main/java/com/yingpai/gupiao/domain/po/User.java

@@ -51,6 +51,11 @@ public class User {
      */
     private Integer status;
     
+    /**
+     * 用户积分
+     */
+    private Integer totalPoints;
+    
     /**
      * 创建时间
      */

+ 63 - 0
src/main/java/com/yingpai/gupiao/domain/po/UserPointsRecord.java

@@ -0,0 +1,63 @@
+package com.yingpai.gupiao.domain.po;
+
+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;
+
+/**
+ * 积分流水记录实体类
+ * 对应数据库表 user_points_record
+ */
+@Data
+@TableName("user_points_record")
+public class UserPointsRecord {
+    
+    /**
+     * 主键ID,自增
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    
+    /**
+     * 类型:1-收入 2-支出
+     */
+    private Integer type;
+    
+    /**
+     * 积分数量
+     */
+    private Integer amount;
+    
+    /**
+     * 变动后余额
+     */
+    private Integer balance;
+    
+    /**
+     * 业务类型:1-注册奖励 2-充值 3-订阅 4-活动奖励
+     */
+    private Integer bizType;
+    
+    /**
+     * 关联业务ID
+     */
+    private String bizId;
+    
+    /**
+     * 备注说明
+     */
+    private String remark;
+    
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 52 - 0
src/main/java/com/yingpai/gupiao/domain/vo/PointsRecordVO.java

@@ -0,0 +1,52 @@
+package com.yingpai.gupiao.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 积分记录VO
+ */
+@Data
+@Builder
+public class PointsRecordVO {
+    
+    /**
+     * 记录ID
+     */
+    private Long id;
+    
+    /**
+     * 类型:1-收入 2-支出
+     */
+    private Integer type;
+    
+    /**
+     * 积分数量
+     */
+    private Integer amount;
+    
+    /**
+     * 变动后余额
+     */
+    private Integer balance;
+    
+    /**
+     * 业务类型:1-注册 2-充值 3-订阅 4-奖励
+     */
+    private Integer bizType;
+    
+    /**
+     * 业务类型名称(中文)
+     */
+    private String bizTypeName;
+    
+    /**
+     * 备注说明
+     */
+    private String remark;
+    
+    /**
+     * 创建时间(时间戳)
+     */
+    private Long createTime;
+}

+ 17 - 0
src/main/java/com/yingpai/gupiao/domain/vo/UserPointsVO.java

@@ -0,0 +1,17 @@
+package com.yingpai.gupiao.domain.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 用户积分VO
+ */
+@Data
+@Builder
+public class UserPointsVO {
+    
+    /**
+     * 当前积分
+     */
+    private Integer totalPoints;
+}

+ 18 - 0
src/main/java/com/yingpai/gupiao/mapper/SysDictDataMapper.java

@@ -0,0 +1,18 @@
+package com.yingpai.gupiao.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 字典数据Mapper
+ */
+@Mapper
+public interface SysDictDataMapper {
+    
+    /**
+     * 根据字典类型和键值查询标签
+     */
+    @Select("SELECT dict_label FROM sys_dict_data WHERE dict_type = #{dictType} AND dict_value = #{dictValue} LIMIT 1")
+    String selectLabelByTypeAndValue(@Param("dictType") String dictType, @Param("dictValue") String dictValue);
+}

+ 12 - 0
src/main/java/com/yingpai/gupiao/mapper/UserPointsRecordMapper.java

@@ -0,0 +1,12 @@
+package com.yingpai.gupiao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yingpai.gupiao.domain.po.UserPointsRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 积分流水记录Mapper接口
+ */
+@Mapper
+public interface UserPointsRecordMapper extends BaseMapper<UserPointsRecord> {
+}

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

@@ -25,7 +25,7 @@ public interface AuthService {
     
     /**
      * 第二步:手机号授权验证
-     * @param dto 包含loginCode和phoneCode
+     * @param dto 包含loginCode和加密phone
      * @return 已注册返回token,未注册返回用户信息
      */
     WxLoginCheckVO wxPhoneCheck(WxPhoneLoginDTO dto);

+ 31 - 0
src/main/java/com/yingpai/gupiao/service/DictService.java

@@ -0,0 +1,31 @@
+package com.yingpai.gupiao.service;
+
+import com.yingpai.gupiao.mapper.SysDictDataMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 字典服务
+ */
+@Service
+@RequiredArgsConstructor
+public class DictService {
+    
+    private final SysDictDataMapper dictDataMapper;
+    
+    /**
+     * 积分业务类型字典
+     */
+    public static final String DICT_POINTS_BIZ_TYPE = "points_biz_type";
+    
+    /**
+     * 根据字典类型和键值获取标签
+     */
+    public String getDictLabel(String dictType, Integer dictValue) {
+        if (dictValue == null) {
+            return "";
+        }
+        String label = dictDataMapper.selectLabelByTypeAndValue(dictType, String.valueOf(dictValue));
+        return label != null ? label : "";
+    }
+}

+ 53 - 0
src/main/java/com/yingpai/gupiao/service/UserPointsService.java

@@ -0,0 +1,53 @@
+package com.yingpai.gupiao.service;
+
+import com.yingpai.gupiao.domain.po.UserPointsRecord;
+
+import java.util.List;
+
+/**
+ * 用户积分服务接口
+ */
+public interface UserPointsService {
+    
+    /**
+     * 初始化用户积分(注册时调用)
+     * @param userId 用户ID
+     * @param initialPoints 初始积分
+     */
+    void initUserPoints(Long userId, int initialPoints);
+    
+    /**
+     * 获取用户积分
+     * @param userId 用户ID
+     * @return 积分数量
+     */
+    Integer getUserPoints(Long userId);
+    
+    /**
+     * 增加积分
+     * @param userId 用户ID
+     * @param amount 积分数量
+     * @param bizType 业务类型:1-注册 2-充值 3-订阅 4-奖励
+     * @param bizId 业务ID
+     * @param remark 备注
+     */
+    void addPoints(Long userId, int amount, int bizType, String bizId, String remark);
+    
+    /**
+     * 扣减积分
+     * @param userId 用户ID
+     * @param amount 积分数量
+     * @param bizType 业务类型:1-注册 2-充值 3-订阅 4-奖励
+     * @param bizId 业务ID
+     * @param remark 备注
+     * @return 是否成功
+     */
+    boolean deductPoints(Long userId, int amount, int bizType, String bizId, String remark);
+    
+    /**
+     * 获取用户积分记录
+     * @param userId 用户ID
+     * @return 积分记录列表
+     */
+    List<UserPointsRecord> getPointsRecords(Long userId);
+}

+ 16 - 2
src/main/java/com/yingpai/gupiao/service/impl/AuthServiceImpl.java

@@ -9,11 +9,13 @@ 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.service.UserPointsService;
 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;
 
@@ -31,9 +33,15 @@ import java.time.LocalDateTime;
 public class AuthServiceImpl implements AuthService {
     
     private final UserMapper userMapper;
+    private final UserPointsService userPointsService;
     private final JwtUtil jwtUtil;
     private final WxApiUtil wxApiUtil;
     
+    /**
+     * 新用户注册赠送积分数量
+     */
+    private static final int INITIAL_POINTS = 10;
+    
     /**
      * 第一步:微信静默登录(检查是否为老用户)
      * @param loginCode 微信登录code
@@ -179,6 +187,7 @@ public class AuthServiceImpl implements AuthService {
      * @return 返回token
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public LoginVO wxCompleteUserInfo(WxCompleteUserInfoDTO dto) {
         try {
             log.info("【第三步】完善用户信息,openid: {}, phone: {}, nickname: {}", 
@@ -212,6 +221,7 @@ public class AuthServiceImpl implements AuthService {
             newUser.setNickname(dto.getNickname());
             newUser.setAvatar(dto.getAvatarUrl());
             newUser.setStatus(0);
+            newUser.setTotalPoints(INITIAL_POINTS);
             newUser.setCreateTime(LocalDateTime.now());
             newUser.setUpdateTime(LocalDateTime.now());
             
@@ -219,10 +229,14 @@ public class AuthServiceImpl implements AuthService {
             log.info("创建新用户成功,userId: {}, openid: {}, phone: {}, nickname: {}", 
                     newUser.getId(), dto.getOpenid(), dto.getPhoneNumber(), dto.getNickname());
             
-            // 4. 生成token
+            // 4. 记录积分流水
+            userPointsService.initUserPoints(newUser.getId(), INITIAL_POINTS);
+            log.info("用户积分初始化完成,userId: {}, 赠送初始积分: {}", newUser.getId(), INITIAL_POINTS);
+            
+            // 5. 生成token
             String token = jwtUtil.generateToken(newUser.getId(), newUser.getPhone());
             
-            // 5. 返回token
+            // 6. 返回token
             return LoginVO.builder()
                     .token(token)
                     .build();

+ 147 - 0
src/main/java/com/yingpai/gupiao/service/impl/UserPointsServiceImpl.java

@@ -0,0 +1,147 @@
+package com.yingpai.gupiao.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yingpai.gupiao.domain.po.User;
+import com.yingpai.gupiao.domain.po.UserPointsRecord;
+import com.yingpai.gupiao.mapper.UserMapper;
+import com.yingpai.gupiao.mapper.UserPointsRecordMapper;
+import com.yingpai.gupiao.service.UserPointsService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 用户积分服务实现类
+ * 积分存储在user表的total_points字段
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UserPointsServiceImpl implements UserPointsService {
+    
+    private final UserMapper userMapper;
+    private final UserPointsRecordMapper userPointsRecordMapper;
+    
+    /**
+     * 积分类型:收入
+     */
+    private static final int TYPE_INCOME = 1;
+    
+    /**
+     * 积分类型:支出
+     */
+    private static final int TYPE_EXPENSE = 2;
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void initUserPoints(Long userId, int initialPoints) {
+        log.info("记录注册积分流水,userId: {}, initialPoints: {}", userId, initialPoints);
+        
+        // 创建积分流水记录
+        UserPointsRecord record = new UserPointsRecord();
+        record.setUserId(userId);
+        record.setType(TYPE_INCOME);
+        record.setAmount(initialPoints);
+        record.setBalance(initialPoints);
+        record.setBizType(1);  // 注册奖励(对应字典 points_biz_type)
+        record.setRemark("新用户注册奖励");
+        record.setCreateTime(LocalDateTime.now());
+        userPointsRecordMapper.insert(record);
+        
+        log.info("注册积分流水记录完成,userId: {}", userId);
+    }
+    
+    @Override
+    public Integer getUserPoints(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return 0;
+        }
+        return user.getTotalPoints() != null ? user.getTotalPoints() : 0;
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addPoints(Long userId, int amount, int bizType, String bizId, String remark) {
+        log.info("增加积分,userId: {}, amount: {}, bizType: {}", userId, amount, bizType);
+        
+        // 1. 获取用户
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        
+        // 2. 更新积分
+        int currentPoints = user.getTotalPoints() != null ? user.getTotalPoints() : 0;
+        int newBalance = currentPoints + amount;
+        user.setTotalPoints(newBalance);
+        user.setUpdateTime(LocalDateTime.now());
+        userMapper.updateById(user);
+        
+        // 3. 记录流水
+        UserPointsRecord record = new UserPointsRecord();
+        record.setUserId(userId);
+        record.setType(TYPE_INCOME);
+        record.setAmount(amount);
+        record.setBalance(newBalance);
+        record.setBizType(bizType);
+        record.setBizId(bizId);
+        record.setRemark(remark);
+        record.setCreateTime(LocalDateTime.now());
+        userPointsRecordMapper.insert(record);
+        
+        log.info("积分增加完成,userId: {}, newBalance: {}", userId, newBalance);
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deductPoints(Long userId, int amount, int bizType, String bizId, String remark) {
+        log.info("扣减积分,userId: {}, amount: {}, bizType: {}", userId, amount, bizType);
+        
+        // 1. 获取用户
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        
+        // 2. 检查积分是否足够
+        int currentPoints = user.getTotalPoints() != null ? user.getTotalPoints() : 0;
+        if (currentPoints < amount) {
+            log.warn("积分不足,userId: {}, current: {}, need: {}", userId, currentPoints, amount);
+            return false;
+        }
+        
+        // 3. 更新积分
+        int newBalance = currentPoints - amount;
+        user.setTotalPoints(newBalance);
+        user.setUpdateTime(LocalDateTime.now());
+        userMapper.updateById(user);
+        
+        // 4. 记录流水
+        UserPointsRecord record = new UserPointsRecord();
+        record.setUserId(userId);
+        record.setType(TYPE_EXPENSE);
+        record.setAmount(amount);
+        record.setBalance(newBalance);
+        record.setBizType(bizType);
+        record.setBizId(bizId);
+        record.setRemark(remark);
+        record.setCreateTime(LocalDateTime.now());
+        userPointsRecordMapper.insert(record);
+        
+        log.info("积分扣减完成,userId: {}, newBalance: {}", userId, newBalance);
+        return true;
+    }
+    
+    @Override
+    public List<UserPointsRecord> getPointsRecords(Long userId) {
+        LambdaQueryWrapper<UserPointsRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(UserPointsRecord::getUserId, userId)
+               .orderByDesc(UserPointsRecord::getCreateTime);
+        return userPointsRecordMapper.selectList(wrapper);
+    }
+}

+ 46 - 16
src/main/resources/sql/user.sql

@@ -1,16 +1,46 @@
--- 用户表
-CREATE TABLE IF NOT EXISTS `user` (
-  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
-  `openid` VARCHAR(64) DEFAULT NULL COMMENT '微信openid',
-  `unionid` VARCHAR(64) DEFAULT NULL COMMENT '微信unionid',
-  `phone` VARCHAR(11) DEFAULT NULL COMMENT '手机号',
-  `nickname` VARCHAR(50) DEFAULT NULL COMMENT '用户昵称',
-  `avatar` VARCHAR(255) DEFAULT NULL COMMENT '用户头像URL',
-  `status` TINYINT DEFAULT 0 COMMENT '用户状态:0-正常,1-禁用',
-  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `uk_openid` (`openid`),
-  UNIQUE KEY `uk_phone` (`phone`),
-  KEY `idx_create_time` (`create_time`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+/*
+ Navicat Premium Dump SQL
+
+ Source Server         : zbw
+ Source Server Type    : MySQL
+ Source Server Version : 80042 (8.0.42)
+ Source Host           : localhost:3306
+ Source Schema         : ry_vue_5.x
+
+ Target Server Type    : MySQL
+ Target Server Version : 80042 (8.0.42)
+ File Encoding         : 65001
+
+ Date: 24/12/2025 10:40:20
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for user
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user`  (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
+  `openid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '微信openid',
+  `unionid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '微信unionid',
+  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号',
+  `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称',
+  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户头像URL',
+  `status` tinyint NULL DEFAULT 0 COMMENT '用户状态:0-正常,1-禁用',
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `total_points` int NULL DEFAULT 0 COMMENT '用户积分',
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE INDEX `uk_openid`(`openid` ASC) USING BTREE,
+  UNIQUE INDEX `uk_phone`(`phone` ASC) USING BTREE,
+  INDEX `idx_create_time`(`create_time` ASC) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Records of user
+-- ----------------------------
+INSERT INTO `user` VALUES (7, 'oyncZ4yse-EEiGdcT8n10cLPdFoA', NULL, '18871601502', '111', 'http://localhost:8081/uploads/2025/12/24/d65ed8c3-6390-4cfd-8104-9cb721e5a5a2.jpeg', 0, '2025-12-24 10:09:57', '2025-12-24 10:09:57', 0);
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 43 - 0
src/main/resources/sql/user_points_record.sql

@@ -0,0 +1,43 @@
+/*
+ Navicat Premium Dump SQL
+
+ Source Server         : zbw
+ Source Server Type    : MySQL
+ Source Server Version : 80042 (8.0.42)
+ Source Host           : localhost:3306
+ Source Schema         : ry_vue_5.x
+
+ Target Server Type    : MySQL
+ Target Server Version : 80042 (8.0.42)
+ File Encoding         : 65001
+
+ Date: 24/12/2025 10:25:06
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for user_points_record
+-- ----------------------------
+DROP TABLE IF EXISTS `user_points_record`;
+CREATE TABLE `user_points_record`  (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `user_id` bigint NOT NULL COMMENT '用户ID',
+  `type` tinyint NOT NULL COMMENT '类型:1-收入 2-支出',
+  `amount` int NOT NULL COMMENT '积分数量',
+  `balance` int NOT NULL COMMENT '变动后余额',
+  `biz_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '业务类型:RECHARGE-充值 SUBSCRIBE-订阅 REWARD-奖励',
+  `biz_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关联业务ID',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注说明',
+  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`) USING BTREE,
+  INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
+  INDEX `idx_create_time`(`create_time` ASC) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '积分流水记录' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Records of user_points_record
+-- ----------------------------
+
+SET FOREIGN_KEY_CHECKS = 1;

BIN
uploads/2025/12/24/a3cd3c96-7669-4100-b079-db2415f29689.jpeg


BIN
uploads/2025/12/24/c7c146ae-142c-47e1-b7dd-5e9725119c89.jpeg