- 新增 t_todo 表(user_id 隔离 + 逻辑删除),增量脚本 sql/delta_todo.sql - entity/mapper/service/controller 四层,复用 MyBatis-Plus 与 Result - /todo/** 纳入 JWT 认证,user_id 一律由 token 解析,禁止前端传入
This commit is contained in:
@@ -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`
|
||||||
|
);
|
||||||
@@ -81,6 +81,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.permitAll()
|
.permitAll()
|
||||||
.antMatchers("/delta-gun-code/**")
|
.antMatchers("/delta-gun-code/**")
|
||||||
.authenticated()
|
.authenticated()
|
||||||
|
// 待办清单:多人数据隔离,必须登录后访问
|
||||||
|
.antMatchers("/todo/**")
|
||||||
|
.authenticated()
|
||||||
.and()
|
.and()
|
||||||
.formLogin()
|
.formLogin()
|
||||||
.loginProcessingUrl("/login")
|
.loginProcessingUrl("/login")
|
||||||
|
|||||||
@@ -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<Todo> findPageList(long pageNum, long pageSize, Todo todo) {
|
||||||
|
Long userId = getUserId();
|
||||||
|
QueryWrapper<Todo> 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<Todo> 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<Todo> 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<Todo> 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<Todo> 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("删除失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Todo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Todo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<TodoMapper, Todo> implements TodoService {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user