feat: 全面系统检查与修复 — 51项问题修复

## 重大修复

### 安全性修复
- 修复 notification read-all SQL 运算符优先级 bug
- 修复 link_url XSS 漏洞(验证 URL 协议)
- 修复 LIKE 通配符注入(转义 % 和 _)
- 修复 uploadImageIfNeeded 运算符优先级 bug
- 401 时清理所有本地存储(token/nickname/avatar/role/group)

### 数据完整性修复
- 修复公告已读状态共享问题(新建 notification_reads 表)
- 修复群组账单数据缺失(退出群组时保留 group_id)
- 修复群组解散后邀请码仍可加入
- 修复分类迁移未校验目标类型
- 修复群组公告缺少 group_id 必填校验

### 功能修复
- 修复 category PUT /sort 路由冲突
- 修复 GROUP BY 不完整问题
- 修复 budget API 类型不匹配
- 修复 categoryStore.migrateCategory 不刷新本地数据
- 修复 groupStore 并发请求问题
- 修复账单页覆盖 store 数据
- 修复群组预算查询返回 0 而非 null
- 修复通知页面 onShow 不刷新列表
- 修复统计页面不必要的重复请求

### 用户体验优化
- 添加通知详情查看功能(弹窗)
- 添加通知图片服务器上传
- 添加 Markdown 富文本工具栏
- 添加管理页面客户端认证检查
- 添加管理员公告页面下拉刷新
- 添加数据导出进度反馈
- 添加账单删除后筛选金额更新
- Numpad 添加安全区域 padding

### 代码质量提升
- 提取 requireAdmin 为共享中间件
- filter-panel 使用设计 token
- 修复 getCurrentMonth 时区不一致
- 备份功能使用分页查询避免内存问题
- 管理后台仪表盘添加缓存
- 邀请码碰撞重试后报错

## 新增文件
- server/src/middleware/requireAdmin.ts — 共享管理员权限中间件
- client/src/pages/admin/notifications/index.vue — 公告管理页面

## 数据库变更
- 新增 notification_reads 表(公告已读记录)
- 群组解散时保留 groups 记录和 transactions.group_id
This commit is contained in:
wangxiaogang
2026-06-07 15:35:37 +08:00
parent 4f42f2c342
commit bd7af8e512
35 changed files with 1683 additions and 759 deletions

View File

@@ -148,6 +148,23 @@ async function runMigrations(conn: mysql.Connection) {
await conn.query('ALTER TABLE notifications ADD INDEX idx_pinned (is_pinned, created_at)')
await conn.query('ALTER TABLE notifications ADD INDEX idx_expire (expire_at)')
}
// 公告已读记录表(解决系统/群组公告已读状态共享问题)
const hasNotificationReads = await tableExists(conn, 'notification_reads')
if (!hasNotificationReads) {
console.log('[DB] Migrating: creating notification_reads table')
await conn.query(`
CREATE TABLE IF NOT EXISTS notification_reads (
notification_id INT NOT NULL,
user_id INT NOT NULL,
read_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (notification_id, user_id),
INDEX idx_user (user_id),
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)
}
}
export async function initDatabase() {

View File

@@ -81,7 +81,7 @@ CREATE TABLE IF NOT EXISTS notifications (
type ENUM('system', 'group', 'personal') NOT NULL,
title VARCHAR(100) NOT NULL,
content TEXT,
is_read TINYINT(1) DEFAULT 0,
is_read TINYINT(1) DEFAULT 0 COMMENT '仅用于个人通知,系统/群组公告使用 notification_reads 表',
is_pinned TINYINT(1) DEFAULT 0 COMMENT '是否置顶',
is_urgent TINYINT(1) DEFAULT 0 COMMENT '是否强提醒弹窗',
publish_at TIMESTAMP NULL COMMENT '定时发布时间NULL=立即发布)',
@@ -95,6 +95,17 @@ CREATE TABLE IF NOT EXISTS notifications (
INDEX idx_expire (expire_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 公告已读记录表(解决系统/群组公告已读状态共享问题)
CREATE TABLE IF NOT EXISTS notification_reads (
notification_id INT NOT NULL,
user_id INT NOT NULL,
read_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (notification_id, user_id),
INDEX idx_user (user_id),
FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS saved_filters (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,