From e37c13d5413b0ddc3e3544c0ef0da06876ab93d8 Mon Sep 17 00:00:00 2001 From: zhangjiaxun <732128096@qq.com> Date: Fri, 16 Oct 2026 23:27:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(todo):=20=E6=96=B0=E5=A2=9E=E5=A4=9A?= =?UTF-8?q?=E4=BA=BA=E5=BE=85=E5=8A=9E=E6=B8=85=E5=8D=95=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 t_todo 表(user_id 隔离 + 逻辑删除),增量脚本 sql/delta_todo.sql - entity/mapper/service/controller 四层,复用 MyBatis-Plus 与 Result - /todo/** 纳入 JWT 认证,user_id 一律由 token 解析,禁止前端传入 --- sql/delta_todo.sql | 55 +++++++ .../admin/config/WebSecurityConfig.java | 3 + .../admin/controller/TodoController.java | 152 ++++++++++++++++++ .../java/com/freedom/admin/entity/Todo.java | 90 +++++++++++ .../com/freedom/admin/mapper/TodoMapper.java | 13 ++ .../freedom/admin/service/TodoService.java | 13 ++ .../admin/service/impl/TodoServiceImpl.java | 19 +++ 7 files changed, 345 insertions(+) create mode 100644 sql/delta_todo.sql create mode 100644 src/main/java/com/freedom/admin/controller/TodoController.java create mode 100644 src/main/java/com/freedom/admin/entity/Todo.java create mode 100644 src/main/java/com/freedom/admin/mapper/TodoMapper.java create mode 100644 src/main/java/com/freedom/admin/service/TodoService.java create mode 100644 src/main/java/com/freedom/admin/service/impl/TodoServiceImpl.java diff --git a/sql/delta_todo.sql b/sql/delta_todo.sql new file mode 100644 index 0000000..c988e47 --- /dev/null +++ b/sql/delta_todo.sql @@ -0,0 +1,55 @@ +-- ============================================================ +-- 待办清单模块(t_todo) +-- 数据库: freedom_db +-- 用途: 多人待办清单,数据按 user_id 隔离,每个登录用户只能管理自己的待办 +-- 说明: 可重复执行(CREATE TABLE IF NOT EXISTS / 幂等 INSERT 判断) +-- 执行后需重新登录系统(或退出登录)刷新菜单缓存方可看到新菜单 +-- ============================================================ + +USE `freedom_db`; + +-- 1. 待办清单数据表 +CREATE TABLE IF NOT EXISTS `t_todo` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `user_id` BIGINT NOT NULL COMMENT '所属用户ID(关联 t_sys_user.id)', + `title` VARCHAR(200) NOT NULL COMMENT '标题', + `status` TINYINT DEFAULT 0 COMMENT '状态:0未完成 1已完成', + `priority` TINYINT DEFAULT 2 COMMENT '优先级:1低 2中 3高', + `due_date` DATE DEFAULT NULL COMMENT '截止日期', + `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', + `create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建者', + `create_date` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新者', + `update_date` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` TINYINT DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (`id`), + KEY `idx_todo_user_status` (`user_id`, `status`, `del_flag`), + KEY `idx_todo_user_due` (`user_id`, `due_date`) +) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4 COMMENT='待办清单表'; + +-- 2. 菜单权限:一级菜单「待办」 +INSERT INTO `t_sys_permission` + (`parent_id`, `permission_code`, `permission_name`, `permission_desc`, `level`, `icon`, `url`, `component`, `sort`, `create_by`, `create_date`, `del_flag`) +SELECT 0, 'todo', '待办', '多人待办清单', 1, 'md-checkmark-circle', '', '', 10, 'admin', NOW(), 0 +FROM DUAL +WHERE NOT EXISTS (SELECT 1 FROM `t_sys_permission` WHERE `permission_code` = 'todo' AND `del_flag` = 0); + +-- 3. 菜单权限:二级菜单「待办清单」 +SET @todo_parent_id = (SELECT `id` FROM `t_sys_permission` WHERE `permission_code` = 'todo' AND `del_flag` = 0 ORDER BY `id` LIMIT 1); + +INSERT INTO `t_sys_permission` + (`parent_id`, `permission_code`, `permission_name`, `permission_desc`, `level`, `icon`, `url`, `component`, `sort`, `create_by`, `create_date`, `del_flag`) +SELECT @todo_parent_id, 'todo:list', '待办清单', '个人待办事项的增删改与完成状态维护', 2, 'md-list-box', '/todo/list', '/todo/todo-list', 1, 'admin', NOW(), 0 +FROM DUAL +WHERE NOT EXISTS (SELECT 1 FROM `t_sys_permission` WHERE `permission_code` = 'todo:list' AND `del_flag` = 0); + +-- 4. 角色授权:给所有现有角色挂上新菜单(幂等) +INSERT INTO `t_sys_role_permission` (`role_id`, `permission_id`, `create_by`, `create_date`) +SELECT r.`id`, p.`id`, 'admin', NOW() +FROM `t_sys_role` r +JOIN `t_sys_permission` p ON p.`permission_code` IN ('todo', 'todo:list') AND p.`del_flag` = 0 +WHERE r.`del_flag` = 0 + AND NOT EXISTS ( + SELECT 1 FROM `t_sys_role_permission` rp + WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id` + ); diff --git a/src/main/java/com/freedom/admin/config/WebSecurityConfig.java b/src/main/java/com/freedom/admin/config/WebSecurityConfig.java index 81f5905..b00e6c0 100644 --- a/src/main/java/com/freedom/admin/config/WebSecurityConfig.java +++ b/src/main/java/com/freedom/admin/config/WebSecurityConfig.java @@ -81,6 +81,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .permitAll() .antMatchers("/delta-gun-code/**") .authenticated() + // 待办清单:多人数据隔离,必须登录后访问 + .antMatchers("/todo/**") + .authenticated() .and() .formLogin() .loginProcessingUrl("/login") diff --git a/src/main/java/com/freedom/admin/controller/TodoController.java b/src/main/java/com/freedom/admin/controller/TodoController.java new file mode 100644 index 0000000..48b7c2e --- /dev/null +++ b/src/main/java/com/freedom/admin/controller/TodoController.java @@ -0,0 +1,152 @@ +package com.freedom.admin.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.freedom.admin.base.BaseController; +import com.freedom.admin.common.Result; +import com.freedom.admin.entity.Todo; +import com.freedom.admin.service.TodoService; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Date; + +/** + * 控制层-待办清单 + * + * @author zhangjiaxun + * @description 多用户待办,数据按当前登录用户严格隔离(user_id 由 token 解析,禁止前端传入) + */ +@RestController +@RequestMapping("todo") +@Log4j2 +public class TodoController extends BaseController { + + @Autowired + private TodoService todoService; + + /** + * 分页查询当前用户的待办列表 + */ + @RequestMapping("findPageList") + public Result findPageList(long pageNum, long pageSize, Todo todo) { + Long userId = getUserId(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 数据隔离:只查当前登录用户的数据 + queryWrapper.eq("user_id", userId); + queryWrapper.eq("del_flag", 0); + queryWrapper.like(StringUtils.isNotBlank(todo.getTitle()), "title", todo.getTitle()); + queryWrapper.eq(todo.getStatus() != null, "status", todo.getStatus()); + queryWrapper.eq(todo.getPriority() != null, "priority", todo.getPriority()); + // 未完成的排在前面,同状态按创建时间倒序 + queryWrapper.orderByAsc("status").orderByDesc("create_date"); + Page page = todoService.page(new Page<>(pageNum, pageSize), queryWrapper); + return Result.ok().setData(page); + } + + /** + * 新增待办 + */ + @RequestMapping("add") + public Result add(Todo todo) { + if (StringUtils.isBlank(todo.getTitle())) { + return Result.error().message("标题不能为空"); + } + try { + Long userId = getUserId(); + todo.setId(null); + // 归属人一律取当前登录用户,忽略前端可能传入的 userId + todo.setUserId(userId); + if (todo.getStatus() == null) { + todo.setStatus(0); + } + if (todo.getPriority() == null) { + todo.setPriority(2); + } + todo.setCreateBy(getUsername()); + todo.setCreateDate(new Date()); + todo.setUpdateBy(getUsername()); + todo.setUpdateDate(new Date()); + boolean save = todoService.save(todo); + return Result.ok().setData(save); + } catch (Exception e) { + log.error("新增待办失败", e); + return Result.error().message("新增失败"); + } + } + + /** + * 更新待办 + */ + @RequestMapping("update") + public Result update(Todo todo) { + if (todo.getId() == null) { + return Result.error().message("参数缺失"); + } + if (StringUtils.isBlank(todo.getTitle())) { + return Result.error().message("标题不能为空"); + } + try { + Long userId = getUserId(); + todo.setUserId(null); + todo.setUpdateBy(getUsername()); + todo.setUpdateDate(new Date()); + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + // 数据隔离:只允许更新自己的数据 + updateWrapper.eq("id", todo.getId()).eq("user_id", userId).eq("del_flag", 0); + boolean update = todoService.update(todo, updateWrapper); + return Result.ok().setData(update); + } catch (Exception e) { + log.error("更新待办失败", e); + return Result.error().message("更新失败"); + } + } + + /** + * 勾选/取消完成 + */ + @RequestMapping("updateStatus") + public Result updateStatus(Todo todo) { + if (todo.getId() == null || todo.getStatus() == null) { + return Result.error().message("参数缺失"); + } + try { + Long userId = getUserId(); + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", todo.getId()).eq("user_id", userId).eq("del_flag", 0) + .set("status", todo.getStatus()) + .set("update_by", getUsername()) + .set("update_date", new Date()); + boolean update = todoService.update(updateWrapper); + return Result.ok().setData(update); + } catch (Exception e) { + log.error("更新待办状态失败", e); + return Result.error().message("状态更新失败"); + } + } + + /** + * 删除待办(逻辑删除,仅能删除自己的数据) + */ + @RequestMapping("remove") + public Result remove(Todo todo) { + if (todo.getId() == null) { + return Result.error().message("参数缺失"); + } + try { + Long userId = getUserId(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", todo.getId()).eq("user_id", userId); + boolean remove = todoService.remove(queryWrapper); + return Result.ok().setData(remove); + } catch (Exception e) { + log.error("删除待办失败", e); + return Result.error().message("删除失败"); + } + } + +} diff --git a/src/main/java/com/freedom/admin/entity/Todo.java b/src/main/java/com/freedom/admin/entity/Todo.java new file mode 100644 index 0000000..49e950c --- /dev/null +++ b/src/main/java/com/freedom/admin/entity/Todo.java @@ -0,0 +1,90 @@ +package com.freedom.admin.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * t_todo + * 待办清单(数据按 user_id 严格隔离,每个登录用户只能看到自己的待办) + * + * @author zhangjiaxun + */ +@Data +@TableName("t_todo") +public class Todo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 所属用户ID(关联 t_sys_user.id,由后端 token 解析写入,禁止前端传入) + */ + private Long userId; + + /** + * 标题 + */ + private String title; + + /** + * 状态:0未完成 1已完成 + */ + private Integer status; + + /** + * 优先级:1低 2中 3高 + */ + private Integer priority; + + /** + * 截止日期 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date dueDate; + + /** + * 备注 + */ + private String remark; + + /** + * 创建者 + */ + private String createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createDate; + + /** + * 更新者 + */ + private String updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date updateDate; + + /** + * 删除标记 + */ + private Boolean delFlag; + +} diff --git a/src/main/java/com/freedom/admin/mapper/TodoMapper.java b/src/main/java/com/freedom/admin/mapper/TodoMapper.java new file mode 100644 index 0000000..4d672d7 --- /dev/null +++ b/src/main/java/com/freedom/admin/mapper/TodoMapper.java @@ -0,0 +1,13 @@ +package com.freedom.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.freedom.admin.entity.Todo; + +/** + * 数据访问层-待办清单表 + * + * @author zhangjiaxun + */ +public interface TodoMapper extends BaseMapper { + +} diff --git a/src/main/java/com/freedom/admin/service/TodoService.java b/src/main/java/com/freedom/admin/service/TodoService.java new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/src/main/java/com/freedom/admin/service/TodoService.java @@ -0,0 +1,13 @@ +package com.freedom.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.freedom.admin.entity.Todo; + +/** + * 业务层-待办清单表 + * + * @author zhangjiaxun + */ +public interface TodoService extends IService { + +} diff --git a/src/main/java/com/freedom/admin/service/impl/TodoServiceImpl.java b/src/main/java/com/freedom/admin/service/impl/TodoServiceImpl.java new file mode 100644 index 0000000..c320780 --- /dev/null +++ b/src/main/java/com/freedom/admin/service/impl/TodoServiceImpl.java @@ -0,0 +1,19 @@ +package com.freedom.admin.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.freedom.admin.entity.Todo; +import com.freedom.admin.mapper.TodoMapper; +import com.freedom.admin.service.TodoService; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +/** + * 业务层实现-待办清单表 + * + * @author zhangjiaxun + */ +@Service +@Log4j2 +public class TodoServiceImpl extends ServiceImpl implements TodoService { + +}