- 新增表 t_public_todo 及 Entity/Mapper/Service/Controller 四个类 - 对外接口 7 个:list/add/update/updateStatus/remove/removeDone/stats - Controller 不继承 BaseController,不依赖登录态 - WebSecurityConfig 放行 /public-todo/**,与个人待办 /todo/** 隔离 - 落库脚本 sql/public_todo.sql(幂等建表 + 示例数据)
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- 公开待办看板(无需登录)增量脚本
|
||||||
|
-- 用途: 对外分享的待办清单页面数据源,独立于个人待办表 t_todo
|
||||||
|
-- 特性: 幂等,可重复执行
|
||||||
|
-- 执行: freedom_db
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- 1. 公开待办表
|
||||||
|
CREATE TABLE IF NOT EXISTS `t_public_todo` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`title` VARCHAR(200) NOT NULL COMMENT '标题',
|
||||||
|
`remark` VARCHAR(1000) DEFAULT NULL COMMENT '备注',
|
||||||
|
`priority` TINYINT DEFAULT 2 COMMENT '优先级:1低 2中 3高',
|
||||||
|
`category` VARCHAR(50) DEFAULT NULL COMMENT '分类',
|
||||||
|
`due_date` DATE DEFAULT NULL COMMENT '截止日期',
|
||||||
|
`status` TINYINT DEFAULT 0 COMMENT '状态:0未完成 1已完成',
|
||||||
|
`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 '删除标记:0正常 1已删除',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_public_todo_status` (`status`, `del_flag`),
|
||||||
|
KEY `idx_public_todo_due` (`due_date`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公开待办看板表';
|
||||||
|
|
||||||
|
-- 2. 示例数据(仅当表为空时插入,便于首次打开不空白)
|
||||||
|
INSERT INTO `t_public_todo` (`title`, `remark`, `priority`, `category`, `due_date`, `status`, `create_by`)
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT '欢迎使用公开待办看板' AS title,
|
||||||
|
'这个页面无需登录,链接发给谁都能用。点左侧圆圈可以勾选完成。' AS remark,
|
||||||
|
2 AS priority, '想法' AS category, NULL AS due_date, 0 AS status, 'system' AS create_by
|
||||||
|
UNION ALL
|
||||||
|
SELECT '试试按 N 快速新建、按 / 搜索', '快捷键在底部有提示,手机端会自动隐藏。',
|
||||||
|
1, '想法', NULL, 0, 'system'
|
||||||
|
UNION ALL
|
||||||
|
SELECT '数据保存在服务器数据库', '所有人看到的是同一份清单,支持导出/导入 JSON 备份。',
|
||||||
|
3, '工作', DATE_ADD(CURDATE(), INTERVAL 3 DAY), 0, 'system'
|
||||||
|
) t
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `t_public_todo`);
|
||||||
@@ -84,6 +84,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
// 待办清单:多人数据隔离,必须登录后访问
|
// 待办清单:多人数据隔离,必须登录后访问
|
||||||
.antMatchers("/todo/**")
|
.antMatchers("/todo/**")
|
||||||
.authenticated()
|
.authenticated()
|
||||||
|
// 公开待办看板:对外分享,无需登录
|
||||||
|
.antMatchers("/public-todo/**")
|
||||||
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.formLogin()
|
.formLogin()
|
||||||
.loginProcessingUrl("/login")
|
.loginProcessingUrl("/login")
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
package com.freedom.admin.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.freedom.admin.common.Result;
|
||||||
|
import com.freedom.admin.entity.PublicTodo;
|
||||||
|
import com.freedom.admin.service.PublicTodoService;
|
||||||
|
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;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制层-公开待办看板
|
||||||
|
*
|
||||||
|
* @author zhangjiaxun
|
||||||
|
* @description 对外分享的待办清单,无需登录即可访问,全体访问者共享同一份清单。
|
||||||
|
* 与个人待办 t_todo 完全独立,互不影响。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("public-todo")
|
||||||
|
@Log4j2
|
||||||
|
public class PublicTodoController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作人标识(公开页面无登录态,统一记录为 guest)
|
||||||
|
*/
|
||||||
|
private static final String OPERATOR = "guest";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单次返回上限,防止数据量过大拖慢页面
|
||||||
|
*/
|
||||||
|
private static final int MAX_ROWS = 500;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PublicTodoService publicTodoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询公开看板全部待办
|
||||||
|
*
|
||||||
|
* @param status 状态筛选:0未完成 1已完成,null 为全部
|
||||||
|
* @param priority 优先级筛选:1低 2中 3高
|
||||||
|
* @param category 分类筛选
|
||||||
|
* @param keyword 标题/备注关键字
|
||||||
|
* @param sort 排序:created 最新创建 / priority 优先级 / due 截止日期 / title 标题
|
||||||
|
*/
|
||||||
|
@RequestMapping("list")
|
||||||
|
public Result<List<PublicTodo>> list(Integer status, Integer priority, String category,
|
||||||
|
String keyword, String sort) {
|
||||||
|
try {
|
||||||
|
QueryWrapper<PublicTodo> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("del_flag", 0);
|
||||||
|
queryWrapper.eq(status != null, "status", status);
|
||||||
|
queryWrapper.eq(priority != null, "priority", priority);
|
||||||
|
queryWrapper.eq(StringUtils.isNotBlank(category), "category", category);
|
||||||
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
|
String kw = keyword.trim();
|
||||||
|
queryWrapper.and(w -> w.like("title", kw).or().like("remark", kw));
|
||||||
|
}
|
||||||
|
// 排序:由于列表合并了未完成/已完成,统一在服务端排序,前端不再二次处理
|
||||||
|
if ("priority".equals(sort)) {
|
||||||
|
queryWrapper.orderByDesc("priority").orderByAsc("status").orderByDesc("create_date");
|
||||||
|
} else if ("due".equals(sort)) {
|
||||||
|
// 无截止日期的排在最后
|
||||||
|
queryWrapper.orderByAsc("due_date IS NULL").orderByAsc("due_date").orderByAsc("status");
|
||||||
|
} else if ("title".equals(sort)) {
|
||||||
|
queryWrapper.orderByAsc("title");
|
||||||
|
} else {
|
||||||
|
queryWrapper.orderByAsc("status").orderByDesc("create_date");
|
||||||
|
}
|
||||||
|
queryWrapper.last("LIMIT " + MAX_ROWS);
|
||||||
|
List<PublicTodo> list = publicTodoService.list(queryWrapper);
|
||||||
|
return Result.ok().setData(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询公开待办失败", e);
|
||||||
|
return Result.error().message("查询失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增公开待办
|
||||||
|
*/
|
||||||
|
@RequestMapping("add")
|
||||||
|
public Result add(PublicTodo publicTodo) {
|
||||||
|
if (StringUtils.isBlank(publicTodo.getTitle())) {
|
||||||
|
return Result.error().message("标题不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
publicTodo.setId(null);
|
||||||
|
if (publicTodo.getStatus() == null) {
|
||||||
|
publicTodo.setStatus(0);
|
||||||
|
}
|
||||||
|
if (publicTodo.getPriority() == null) {
|
||||||
|
publicTodo.setPriority(2);
|
||||||
|
}
|
||||||
|
publicTodo.setCreateBy(OPERATOR);
|
||||||
|
publicTodo.setCreateDate(new Date());
|
||||||
|
publicTodo.setUpdateBy(OPERATOR);
|
||||||
|
publicTodo.setUpdateDate(new Date());
|
||||||
|
boolean save = publicTodoService.save(publicTodo);
|
||||||
|
return Result.ok().setData(save);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("新增公开待办失败", e);
|
||||||
|
return Result.error().message("新增失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新公开待办(标题 / 备注 / 优先级 / 分类 / 截止日期 / 状态)
|
||||||
|
*/
|
||||||
|
@RequestMapping("update")
|
||||||
|
public Result update(PublicTodo publicTodo) {
|
||||||
|
if (publicTodo.getId() == null) {
|
||||||
|
return Result.error().message("id 不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(publicTodo.getTitle())) {
|
||||||
|
return Result.error().message("标题不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
publicTodo.setUpdateBy(OPERATOR);
|
||||||
|
publicTodo.setUpdateDate(new Date());
|
||||||
|
publicTodo.setCreateBy(null);
|
||||||
|
publicTodo.setCreateDate(null);
|
||||||
|
boolean update = publicTodoService.updateById(publicTodo);
|
||||||
|
return Result.ok().setData(update);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新公开待办失败", e);
|
||||||
|
return Result.error().message("更新失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换完成状态
|
||||||
|
*/
|
||||||
|
@RequestMapping("updateStatus")
|
||||||
|
public Result updateStatus(Long id, Integer status) {
|
||||||
|
if (id == null) {
|
||||||
|
return Result.error().message("id 不能为空");
|
||||||
|
}
|
||||||
|
if (status == null) {
|
||||||
|
return Result.error().message("status 不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UpdateWrapper<PublicTodo> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("id", id).eq("del_flag", 0);
|
||||||
|
updateWrapper.set("status", status);
|
||||||
|
updateWrapper.set("update_by", OPERATOR);
|
||||||
|
updateWrapper.set("update_date", new Date());
|
||||||
|
boolean update = publicTodoService.update(updateWrapper);
|
||||||
|
return Result.ok().setData(update);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新公开待办状态失败", e);
|
||||||
|
return Result.error().message("更新失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除公开待办(逻辑删除)
|
||||||
|
*/
|
||||||
|
@RequestMapping("remove")
|
||||||
|
public Result remove(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return Result.error().message("id 不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boolean remove = publicTodoService.removeById(id);
|
||||||
|
return Result.ok().setData(remove);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除公开待办失败", e);
|
||||||
|
return Result.error().message("删除失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除已完成的待办(逻辑删除)
|
||||||
|
*/
|
||||||
|
@RequestMapping("removeDone")
|
||||||
|
public Result removeDone() {
|
||||||
|
try {
|
||||||
|
UpdateWrapper<PublicTodo> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("status", 1).eq("del_flag", 0);
|
||||||
|
updateWrapper.set("del_flag", 1);
|
||||||
|
updateWrapper.set("update_by", OPERATOR);
|
||||||
|
updateWrapper.set("update_date", new Date());
|
||||||
|
boolean update = publicTodoService.update(updateWrapper);
|
||||||
|
return Result.ok().setData(update);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量删除已完成待办失败", e);
|
||||||
|
return Result.error().message("清理失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 看板统计(总数 / 未完成 / 已完成 / 已逾期 / 分类分布)
|
||||||
|
*/
|
||||||
|
@RequestMapping("stats")
|
||||||
|
public Result<Map<String, Object>> stats() {
|
||||||
|
try {
|
||||||
|
QueryWrapper<PublicTodo> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("del_flag", 0);
|
||||||
|
List<PublicTodo> list = publicTodoService.list(queryWrapper);
|
||||||
|
|
||||||
|
int total = list.size();
|
||||||
|
int done = 0;
|
||||||
|
int overdue = 0;
|
||||||
|
Map<String, Integer> byCategory = new HashMap<>();
|
||||||
|
Date today = new Date();
|
||||||
|
for (PublicTodo item : list) {
|
||||||
|
if (item.getStatus() != null && item.getStatus() == 1) {
|
||||||
|
done++;
|
||||||
|
} else if (item.getDueDate() != null && item.getDueDate().before(today)) {
|
||||||
|
overdue++;
|
||||||
|
}
|
||||||
|
String cat = StringUtils.isBlank(item.getCategory()) ? "未分类" : item.getCategory();
|
||||||
|
byCategory.merge(cat, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("total", total);
|
||||||
|
data.put("done", done);
|
||||||
|
data.put("active", total - done);
|
||||||
|
data.put("overdue", overdue);
|
||||||
|
data.put("byCategory", byCategory);
|
||||||
|
return Result.ok().setData(data);
|
||||||
|
} 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_public_todo
|
||||||
|
* 公开待办看板(无需登录即可访问,全体访问者共享同一份清单)
|
||||||
|
*
|
||||||
|
* @author zhangjiaxun
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("t_public_todo")
|
||||||
|
public class PublicTodo implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级:1低 2中 3高
|
||||||
|
*/
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分类
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截止日期
|
||||||
|
*/
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0未完成 1已完成
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
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.PublicTodo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据访问层-公开待办看板表
|
||||||
|
*
|
||||||
|
* @author zhangjiaxun
|
||||||
|
*/
|
||||||
|
public interface PublicTodoMapper extends BaseMapper<PublicTodo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.freedom.admin.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.freedom.admin.entity.PublicTodo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务层-公开待办看板表
|
||||||
|
*
|
||||||
|
* @author zhangjiaxun
|
||||||
|
*/
|
||||||
|
public interface PublicTodoService extends IService<PublicTodo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.freedom.admin.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.freedom.admin.entity.PublicTodo;
|
||||||
|
import com.freedom.admin.mapper.PublicTodoMapper;
|
||||||
|
import com.freedom.admin.service.PublicTodoService;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务层实现-公开待办看板表
|
||||||
|
*
|
||||||
|
* @author zhangjiaxun
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Log4j2
|
||||||
|
public class PublicTodoServiceImpl extends ServiceImpl<PublicTodoMapper, PublicTodo> implements PublicTodoService {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user