# points **Repository Path**: sanlinspace/points ## Basic Information - **Project Name**: points - **Description**: 用户积分系统,完全vibe coding,用于练习 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-18 - **Last Updated**: 2026-04-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 用户积分系统 ## 系统功能 - **用户注册与登录**:用户可以注册账号并登录系统 - **每日签到**:用户每天可以签到获取积分,每周重置签到规则 - **积分管理**:用户可以查看积分记录和总积分 - **用户中心**:用户可以查看个人信息和积分情况 ## 积分规则 - **每日签到积分规则**: - 第1天:10积分 - 第2天:15积分 - 第3天:20积分 - 第4天:25积分 - 第5天:30积分 - 第6天:35积分 - 第7天:50积分 - **每周重置**:每周一重置签到规则,重新从第1天开始计算 ## 系统架构 - **后端**:Spring Boot 2.7.18 + JDK 21 + Maven - **前端**:Thymeleaf + HTML + CSS + JavaScript - **数据库**:MySQL 8 - **缓存**:Redis - **认证**:基于Token的认证机制 ## 项目结构 ``` src/ ├── main/ │ ├── java/com/example/points_system/ │ │ ├── common/ # 通用类 │ │ │ ├── BusinessException.java # 自定义业务异常 │ │ │ ├── GlobalExceptionHandler.java # 全局异常处理器 │ │ │ └── Result.java # 统一响应结果封装 │ │ ├── config/ # 配置类 │ │ ├── controller/ # 控制器 │ │ ├── context/ # 上下文 │ │ ├── entity/ # 实体类 │ │ ├── filter/ # 过滤器 │ │ ├── repository/ # 仓库接口 │ │ ├── service/ # 服务类 │ │ └── PointsSystemApplication.java # 应用启动类 │ └── resources/ │ ├── templates/ # Thymeleaf模板 │ └── application.properties # 配置文件 └── test/ # 测试类 ``` ## 通用功能概述 `common` 包提供系统级通用功能,包括: - **BusinessException**:自定义业务异常类,继承自 RuntimeException,支持自定义错误码,用于处理业务逻辑中的各种异常情况(如用户已存在、密码错误、token无效等) - **GlobalExceptionHandler**:全局异常处理器,使用 @RestControllerAdvice 注解,实现对 BusinessException、RuntimeException 及其他异常的统一捕获和处理,返回标准化的错误响应 - **Result**:统一响应结果封装类,用于 Controller 层返回标准化的 JSON 响应格式,包含 success、data、message、code 字段 - **UserInfo**:用户信息类,用于存储用户的核心信息(id、username、email、role、menus),在登录时存储到 Redis 中 - **Role**:角色类,用于存储角色信息(id、name、description、menus) - **Menu**:菜单类,用于存储菜单权限信息(id、name、path、icon、sort) - **RolePermissionUtil**:角色权限工具类,用于处理超级管理员角色逻辑和菜单权限管理 ## 数据库设计 ### 1. 用户表(points_user) | 字段名 | 数据类型 | 描述 | | --- | --- | --- | | id | BIGINT | 用户ID,主键 | | username | VARCHAR(50) | 用户名,唯一 | | password | VARCHAR(100) | 密码,MD5加密 | | email | VARCHAR(100) | 邮箱,唯一 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | ### 2. 积分表(points_point) | 字段名 | 数据类型 | 描述 | | --- | --- | --- | | id | BIGINT | 积分记录ID,主键 | | user_id | BIGINT | 用户ID,外键 | | points | INT | 积分数量(累计) | | description | VARCHAR(255) | 积分描述 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | ### 3. 积分历史记录表(points_history) | 字段名 | 数据类型 | 描述 | | --- | --- | --- | | id | BIGINT | 历史记录ID,主键 | | user_id | BIGINT | 用户ID,外键 | | points | INT | 积分数量 | | type | VARCHAR(50) | 积分类型 | | description | VARCHAR(255) | 积分描述 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | ### 4. 签到记录表(points_check_in) | 字段名 | 数据类型 | 描述 | | --- | --- | --- | | id | BIGINT | 签到记录ID,主键 | | user_id | BIGINT | 用户ID,外键 | | check_in_date | DATE | 签到日期 | | week_of_year | INT | 年周数 | | consecutive_days | INT | 连续签到天数 | | points | INT | 此次签到获得的积分 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | ## 接口设计 ### 1. 认证接口 - **POST /api/auth/register**:用户注册 - **POST /api/auth/login**:用户登录 ### 2. 用户接口 - **GET /api/user/info**:获取用户信息 ### 3. 签到接口 - **GET /api/checkin/status**:检查签到状态 - **POST /api/checkin/do**:执行签到 - **GET /api/checkin/week**:获取本周签到记录 ### 4. 积分接口 - **GET /api/point/records**:获取积分记录 - **GET /api/point/total**:获取总积分 ## 接口幂等性设计 - **签到接口**:使用Redis实现幂等性,在签到请求处理前,生成一个基于用户ID和日期的唯一键,设置5分钟过期时间。如果该键已存在,则拒绝重复请求。 - **其他接口**:对于查询类接口,天然具有幂等性;对于修改类接口,使用数据库事务和唯一约束确保幂等性。 ## 如何启动 ### 1. 环境准备 - JDK 21+ - Maven 3.6+ - MySQL 8 - Redis ### 2. 数据库配置 - 创建数据库:`test1` - 用户名:`root` - 密码:`123456` ### 3. 启动项目 1. 克隆项目到本地 2. 进入项目目录 3. 执行 `mvn clean install` 构建项目 4. 执行 `mvn spring-boot:run` 启动项目 ### 4. 访问项目 - 首页:`http://localhost:8080` - 登录页面:`http://localhost:8080/login` - 注册页面:`http://localhost:8080/register` - 用户中心:`http://localhost:8080/user` ## 如何测试 ### 1. 注册用户 - 访问注册页面,填写用户名、密码和邮箱,点击注册 - 注册成功后跳转到登录页面 ### 2. 登录用户 - 访问登录页面,填写用户名和密码,点击登录 - 登录成功后跳转到用户中心页面 ### 3. 测试签到功能 - 在用户中心页面,点击"立即签到"按钮 - 签到成功后,按钮会变为"今日已签到",并显示签到成功的消息 - 刷新页面后,积分会更新 ### 4. 查看积分记录 - 在用户中心页面,查看积分记录列表 - 可以看到每次签到获得的积分 ## 技术亮点 1. **基于Token的认证机制**:使用UUID生成token,存储在Redis中,有效期24小时 2. **接口幂等性**:使用Redis实现签到接口的幂等性,防止重复签到 3. **分布式缓存**:使用Redis缓存用户token和签到状态,提高系统性能 4. **响应式前端**:使用JavaScript实现异步请求,提升用户体验 5. **安全密码存储**:使用MD5加密存储密码,保障用户数据安全 6. **角色权限系统**:设计了完整的角色权限体系,包含超级管理员角色和预制菜单权限 7. **主页布局**:实现了现代化的主页布局,包含上部分、左侧菜单和右下内容区 8. **预制数据**:提供了完整的预制SQL脚本,包含角色、菜单和用户初始数据 ## 注意事项 1. 确保MySQL和Redis服务已启动 2. 数据库连接信息已在application.properties中配置 3. 项目使用的是Spring Boot 2.7.18版本,JDK 21版本 4. 前端页面使用Thymeleaf模板引擎,无需单独部署 ## 提示词 任务:设计一个用户积分系统。基于jdk21+springboot2.7.18+maven+thymeleaf+reids+mysql8+jpa,搭建一个演示项目,包括前端和后端,前端使用thymeleaf。 1. 代码需要详细注释,详细描述演示什么功能; 2. 页面用于发送演示测试请求; 3. 帮我设计用户表points_user、积分表points_point,包含的字段你帮我补充;如果还需要额外的表,比如日志记录表或其他表,你帮我设计; 4. 设计一个一周每日签到系统,每日签到都能领积分,积分规则你帮我设计,每周重置规则; 5. 当日是否签到的判断需要你帮我设计逻辑,并在代码中详细注释;每个接口需要考虑接口幂等性(并发提交,接口重放都不会出错); 6. 用户的注册和登录功能:用户必须登录后才能领取积分;登录的用户的每个前端请求的请求头都带一个请求头token(值是个UUID)用于后端校验,后端在filter中校验, 校验通过后将token对应的用户信息存放到当前上下文; 7. 用户的个人中心:可以查看用户信息,查看积分情况; 8. 生成readme.md,详细描述系统功能,积分规则,架构,项目结构,如何启动,如何测试,接口幂等性如何设计的等; mysql8相关参数:本地ip,端口3306,用户名root,密码123456,库test1;redis参数:本地ip,6379端口,没设置密码。 ## 提示词历史 1. 设计一个pojo类用户封装Controller放回信息,包括字段success,data, message, code,并更新Controller。 2. 定义一个自定义异常,放到common包下,修改代码中所有的RuntimeException为这个自定义异常。定义一个全局异常处理类,放到common包下。然后更新readme.md,加上一个通用功能概述,通用功能概述用于描述common包中类的功能。 3. UserService#login()方法中,"将token和用户信息存储到Redis",存储的用户信息为一个UserInfo类,UserInfo类放到common包下,更新readme.md 4. 修改application.properties为yml格式,并添加profile机制,添加local和beta两种环境,local环境日志级别debug,beta日志级别info 5. AuthFilter中响应Result,格式为json;AuthFilter中ObjectMapper不要新增一个,而是使用一个全局定义的spring bean 6. 修复UserService#getUserByToken()中,redisService.get的错误:LinkedHashMap cannot be cast to class com.example.points_system.common.UserInfo 7. 设计一套【用户角色权限】功能,角色可以配置并预制超级管理员角色,超级管理员包含所有权限(在代码中体现即可,无需数据库里面配置权限); - 权限暂时以菜单来体现;预制四个菜单:用户管理,角色管理,权限管理,积分管理; - 用户登录后,角色和权限信息也要考虑包含进入,修改UserInfo添加权限信息; - 设计一个主页,当用户登录后自动跳转到主页。主页分为三部分,上部分,左侧菜单,右下内容区。上部分有系统名称,当前登录用户,个人中心图标(点击进入个人中心); - 预制sql写入resources/docs下; 8. 完善主页index.html的用户管理菜单的功能:点击用户管理时,用户管理页面出现在index.html的右侧内容区,可以对用户进行增删改查。 9. 点击index菜单,比如点击用户管理菜单,用户管理userManage.html展示在右侧内容区,请改为使用iframe来实现。 10. 重构userManage.html, roleManage.html,提取出公共组件,和公共样式。 11. userManage.html列表添加字段“角色”,操作添加“添加角色”,点击“添加角色”弹框,可以给用户添加角色 12. 完善主页index.html的权限管理菜单的功能:点击权限管理时,角色管理页面出现在index.html的右侧内容区,可以对权限进行增删改查。 13. 完善主页index.html的积分管理菜单的功能:点击积分管理时,角色管理页面出现在index.html的右侧内容区,可以对积分进行分页列表查询和搜索。 14. 分析userManage,roleManage,pointManage,permissionManage,提取封装一个列表控件,然后这四个页面使用这个封装的页面。 15. 用户中心页面user.html的积分记录下面用列表展示本周签到记录 16. userManage页面的“添加用户”按钮点击后,弹框有上角有个×图片,点击×图标可以关闭弹框 17. userManage页面的“提交”按钮点击后,发送请求,请求到结果判断success=false,message不为空,需要弹出提示。弹出提示封装成公共组件。 18. 积分管理页面操作列添加“积分历史”按钮,点击该按钮弹框,列表展示积分历史记录,弹框的列表也使用公共组件listComponent。 19. ## 开发中出现过的问题记录 ### 循环依赖问题 #### 情况 在添加全局ObjectMapper Bean并在AuthFilter中使用时,出现了循环依赖错误。 #### 原因 - `WebConfig` 类通过 `@Autowired` 注入 `AuthFilter` - `AuthFilter` 通过 `@Autowired` 注入 `ObjectMapper` - `WebConfig` 又定义了 `ObjectMapper` Bean 形成了 `WebConfig` → `AuthFilter` → `ObjectMapper` → `WebConfig` 的循环依赖链。 #### 解决办法 - 移除 `WebConfig` 中的 `@Autowired AuthFilter` 字段 - 将 `AuthFilter` 作为参数传递给 `authFilterRegistrationBean` 方法 - 这样 Spring 会在创建 `FilterRegistrationBean` 时自动注入 `AuthFilter`,避免了循环依赖 - 保持 `AuthFilter` 中对 `ObjectMapper` 的 `@Autowired` 注入 - 保持 `objectMapper()` Bean 的定义 ### 前端Uncaught ReferenceError: listComponent is not defined问题 #### 情况 在角色管理和权限管理页面点击添加按钮时,出现了 `Uncaught ReferenceError: listComponent is not defined` 错误。 #### 原因 - `listComponent.js` 文件中,多个地方使用了全局的 `listComponent` 变量,包括添加按钮、搜索按钮、分页按钮以及表格中的编辑删除按钮 - 当在角色管理和权限管理页面中创建组件实例时,这些实例可能不是以 `listComponent` 命名的,导致出现引用错误 #### 解决办法 - 移除了所有内联的 `onclick` 属性 - 为所有按钮添加了适当的类名或 data 属性 - 在 `bindEvents` 方法中添加了事件委托,处理所有按钮的点击事件 - 使用 `this` 引用替代全局的 `listComponent` 变量 - 这样无论组件实例的变量名是什么,都能正确工作 ### 前端弹框中列表无法渲染问题 #### 情况 在积分管理页面点击积分历史按钮时,弹框发送请求成功拿到返回数据,但页面没有展示数据。 #### 原因 - `listComponent.js` 中的方法使用 `document.getElementById` 全局搜索元素 - 当在弹框中创建多个 ListComponent 实例时,会出现 ID 冲突 - 导致弹框中的数据无法正确渲染 #### 解决办法 修改了 ListComponent 的所有方法,让它们在容器上下文中搜索元素: - `renderTable` 方法:使用 `container.querySelector('#dataTableBody')` - `renderPagination` 方法:使用 `container.querySelector('#pagination')` - `search` 方法:使用 `container.querySelector('#searchKeyword')` 和 `container.querySelector('#pagination')` - `showMessage` 方法:使用 `container.querySelector('#messageBox')` - `bindEvents` 方法:使用 `container.querySelector('#searchKeyword')` - 这样每个 ListComponent 实例都会在自己的容器内搜索元素,避免了 ID 冲突