diff --git a/src/main/resources/sql/archive/notice_add_sch_id.sql b/src/main/resources/sql/archive/notice_add_sch_id.sql new file mode 100644 index 0000000..a6d160d --- /dev/null +++ b/src/main/resources/sql/archive/notice_add_sch_id.sql @@ -0,0 +1,16 @@ +-- ============================================================ +-- notice 表补 sch_id 列(多租户隔离) +-- 决策 D3:发布时从 BoaUser.currentRole.schId 填充,查询时按校过滤 +-- +-- 对应实体:Notice.schId / NoticeRepository 多租户统计方法 +-- +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑 +-- MySQL 5.7+ 兼容 +-- ============================================================ + +-- 1) 加列 +ALTER TABLE `notice` + ADD COLUMN `sch_id` INT NULL COMMENT '学校ID(多租户隔离:发布时从当前用户角色填充)'; + +-- 2) 加索引(用于按学校过滤查询) +CREATE INDEX `idx_notice_sch_id` ON `notice` (`sch_id`); diff --git a/src/main/resources/sql/archive/notice_data_backfill.sql b/src/main/resources/sql/archive/notice_data_backfill.sql new file mode 100644 index 0000000..e0dcbdd --- /dev/null +++ b/src/main/resources/sql/archive/notice_data_backfill.sql @@ -0,0 +1,159 @@ +-- ============================================================ +-- 存量数据回填脚本 +-- 1. notice.sch_id:按发布人所属学校回填(从 sys_user_role_rel 取 main=1 的 sch_id) +-- 2. notice_receiver:从 notice.receive_list JSON 字符串展开为关联表行 +-- +-- 幂等设计:可重复执行,已存在的数据不会重复插入 +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑 +-- MySQL 5.7+ 兼容 +-- ============================================================ + +-- ============================================================ +-- Part 1:回填 notice.sch_id +-- ============================================================ + +-- 方式一:如果 base_user / sys_user 表有 sch_id 字段,直接关联回填 +-- 注意:需根据实际平台库表结构调整关联条件 +UPDATE `notice` n + INNER JOIN ( + SELECT DISTINCT u.login_name, u.sch_id + FROM base_user u + WHERE u.sch_id IS NOT NULL + ) t ON n.publisher_account = t.login_name +SET n.sch_id = t.sch_id +WHERE n.sch_id IS NULL + AND n.publisher_account IS NOT NULL + AND n.publisher_account != ''; + +-- 方式二(备用):如果用户学校关系在 sys_user_role_rel 表 +-- 取每个用户的主身份学校(is_main=1),回填 sch_id +-- 取消注释后执行(视实际库表结构选用) +-- +-- UPDATE `notice` n +-- INNER JOIN ( +-- SELECT r.account, r.sch_id +-- FROM sys_user_role_rel r +-- WHERE r.is_main = 1 +-- AND r.sch_id IS NOT NULL +-- ) t ON n.publisher_account = t.account +-- SET n.sch_id = t.sch_id +-- WHERE n.sch_id IS NULL +-- AND n.publisher_account IS NOT NULL +-- AND n.publisher_account != ''; + + +-- ============================================================ +-- Part 2:从 notice.receive_list 展开到 notice_receiver 关联表 +-- ============================================================ + +-- 说明: +-- - notice.receive_list 存储格式有两种: +-- 1) "ALL"(全员通知)—— 不写明细行,跳过 +-- 2) JSON 数组 ["account1","account2",...] 或逗号分隔串 +-- - 本脚本使用 MySQL JSON 函数解析(MySQL 5.7+ 支持 JSON_EXTRACT) +-- - 对于非标准 JSON 格式(逗号分隔串),使用循环逐条插入 +-- - 幂等:每次插入前先删除该通知的旧接收人记录 + +-- 注意:MySQL 5.7 的 JSON_TABLE 不可用,这里用存储过程逐条解析 +-- 如果 receive_list 是标准 JSON 数组格式,可以直接用 JSON_EXTRACT + +DELIMITER $$ + +-- 存储过程:展开单个通知的接收人列表 +DROP PROCEDURE IF EXISTS `expand_notice_receivers`$$ +CREATE PROCEDURE `expand_notice_receivers`() +BEGIN + DECLARE v_done INT DEFAULT 0; + DECLARE v_notice_id INT; + DECLARE v_receive_list TEXT; + DECLARE cur CURSOR FOR + SELECT id, receive_list + FROM notice + WHERE receive_list IS NOT NULL + AND TRIM(receive_list) != '' + AND UPPER(TRIM(receive_list)) != 'ALL' + AND NOT EXISTS ( + SELECT 1 FROM notice_receiver WHERE notice_id = notice.id LIMIT 1 + ); + DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1; + + OPEN cur; + read_loop: LOOP + FETCH cur INTO v_notice_id, v_receive_list; + IF v_done = 1 THEN + LEAVE read_loop; + END IF; + + -- 清理旧数据(幂等) + DELETE FROM notice_receiver WHERE notice_id = v_notice_id; + + -- 尝试 JSON 数组解析 + -- receive_list 格式: ["account1","account2"] 或 account1,account2 + SET v_receive_list = TRIM(v_receive_list); + + -- 去除首尾方括号(如有) + IF LEFT(v_receive_list, 1) = '[' AND RIGHT(v_receive_list, 1) = ']' THEN + SET v_receive_list = SUBSTRING(v_receive_list, 2, CHAR_LENGTH(v_receive_list) - 2); + END IF; + + -- 替换引号为空,按逗号分隔 + SET v_receive_list = REPLACE(REPLACE(v_receive_list, '"', ''), '''', ''); + + -- 逐条插入 + -- 简单实现:用 FIND_IN_SET 不适用于变量,改用临时表 + BEGIN + DECLARE v_pos INT DEFAULT 1; + DECLARE v_account VARCHAR(64); + DECLARE v_remaining TEXT; + SET v_remaining = v_receive_list; + + WHILE v_remaining IS NOT NULL AND TRIM(v_remaining) != '' DO + IF LOCATE(',', v_remaining) > 0 THEN + SET v_account = TRIM(SUBSTRING_INDEX(v_remaining, ',', 1)); + SET v_remaining = SUBSTRING(v_remaining, LOCATE(',', v_remaining) + 1); + ELSE + SET v_account = TRIM(v_remaining); + SET v_remaining = NULL; + END IF; + + IF v_account != '' AND v_account IS NOT NULL THEN + INSERT INTO notice_receiver (notice_id, account, receive_type, create_time) + VALUES (v_notice_id, v_account, 1, NOW()); + END IF; + END WHILE; + END; + END LOOP; + + CLOSE cur; +END$$ + +DELIMITER ; + +-- 执行存储过程 +CALL expand_notice_receivers(); + +-- 清理临时存储过程 +DROP PROCEDURE IF EXISTS `expand_notice_receivers`; + +-- ============================================================ +-- 回填结果验证查询(执行后手动检查) +-- ============================================================ + +-- 检查1:sch_id 回填覆盖率 +-- SELECT +-- COUNT(*) AS total, +-- SUM(CASE WHEN sch_id IS NOT NULL THEN 1 ELSE 0 END) AS sch_id_filled, +-- SUM(CASE WHEN sch_id IS NULL THEN 1 ELSE 0 END) AS sch_id_null +-- FROM notice; + +-- 检查2:notice_receiver 展开行数 +-- SELECT COUNT(*) FROM notice_receiver; + +-- 检查3:未展开的通知(receive_list 非空非 ALL 但 receiver 表无记录) +-- SELECT n.id, n.receive_list +-- FROM notice n +-- LEFT JOIN notice_receiver r ON r.notice_id = n.id +-- WHERE n.receive_list IS NOT NULL +-- AND TRIM(n.receive_list) != '' +-- AND UPPER(TRIM(n.receive_list)) != 'ALL' +-- AND r.id IS NULL; diff --git a/src/main/resources/sql/archive/notice_receiver_create.sql b/src/main/resources/sql/archive/notice_receiver_create.sql new file mode 100644 index 0000000..780bfff --- /dev/null +++ b/src/main/resources/sql/archive/notice_receiver_create.sql @@ -0,0 +1,24 @@ +-- ============================================================ +-- notice_receiver 建表(替代 notice.receive_list JSON 字符串存储) +-- 决策 D2-B:拆关联表,支撑阅读情况/催收/统计 SQL 化 +-- +-- 对应实体:com.yunhai.notice.entity.NoticeReceiver +-- 索引:idx_notice_id(notice_id) + idx_account(account) +-- +-- 执行方式:由 DBA 手动在对应环境执行,AI 不代跑 +-- MySQL 5.7+ 兼容 +-- ============================================================ + +CREATE TABLE IF NOT EXISTS `notice_receiver` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '自增主键', + `notice_id` INT NOT NULL COMMENT '通知ID(关联 notice.id)', + `account` VARCHAR(64) NOT NULL COMMENT '接收人登录账号', + `receive_type` INT NOT NULL DEFAULT 1 COMMENT '接收类型:1 接收 / 2 抄送', + `create_by` VARCHAR(64) NULL COMMENT '创建人登录账号(审计填充)', + `create_time` DATETIME NULL COMMENT '创建时间(审计填充)', + `update_by` VARCHAR(64) NULL COMMENT '更新人登录账号(审计填充)', + `update_time` DATETIME NULL COMMENT '更新时间(审计填充)', + PRIMARY KEY (`id`), + KEY `idx_notice_id` (`notice_id`), + KEY `idx_account` (`account`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知接收人关联表'; diff --git a/src/main/resources/sql/fa_activity_tables.sql b/src/main/resources/sql/fa_activity_tables.sql new file mode 100644 index 0000000..b3924e3 --- /dev/null +++ b/src/main/resources/sql/fa_activity_tables.sql @@ -0,0 +1,92 @@ +-- ===================================================================== +-- 活动信息发布模块 · 建表脚本(fa_activity / fa_activity_read / fa_activity_signup / operation_log) +-- 说明:应用配置 ddl-auto=update 会自动建表,本脚本供 DBA 手工建库/核对字段使用。 +-- 库:family_activity(本地开发;建库:CREATE DATABASE family_activity DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;) +-- ===================================================================== + +CREATE TABLE IF NOT EXISTS `fa_activity` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', + `sch_id` INT NULL COMMENT '学校ID(数据隔离)', + `title` VARCHAR(200) NULL COMMENT '标题', + `content` TEXT NULL COMMENT '活动内容(富文本)', + `category` VARCHAR(50) NULL COMMENT '活动分类', + `venue` VARCHAR(200) NULL COMMENT '活动地点', + `start_time` DATETIME NULL COMMENT '活动开始时间', + `end_time` DATETIME NULL COMMENT '活动结束时间', + `signup_deadline` DATETIME NULL COMMENT '报名截止时间', + `receipt_mode` VARCHAR(16) NOT NULL DEFAULT 'none' COMMENT '回执方式:none/signup/confirm', + `quota` INT NOT NULL DEFAULT 0 COMMENT '名额上限(0 不限)', + `allow_accompany` INT NOT NULL DEFAULT 0 COMMENT '是否允许家长随行:0/1', + `cost` DECIMAL(10,2) NULL COMMENT '活动费用(元)', + `dept_id` VARCHAR(50) NULL COMMENT '主办部门ID(sys_dict.dict_key)', + `dept_name` VARCHAR(100) NULL COMMENT '主办部门名称(冗余)', + `publisher_id` INT NULL COMMENT '发布人ID(平台用户ID;家长端=学生ID)', + `publisher_name` VARCHAR(50) NULL COMMENT '发布人姓名', + `status` INT NOT NULL DEFAULT 0 COMMENT '发送状态:0 草稿 / 1 已发布', + `topmost` INT NOT NULL DEFAULT 0 COMMENT '置顶:0/1', + `is_pulled` INT NOT NULL DEFAULT 0 COMMENT '撤回:0/1(家长端不可见)', + `timing_send_time` DATETIME NULL COMMENT '定时发送时间', + `sms_flag` INT NOT NULL DEFAULT 0 COMMENT '短信发送标记:0/1(仅存储)', + `receive_type` VARCHAR(16) NOT NULL DEFAULT 'class' COMMENT '接收对象类型:class=班级', + `receive_list` TEXT NULL COMMENT '接收列表:"ALL" 或班级id逗号串', + `source_id` INT NULL COMMENT '转发/复用源活动ID', + `create_by` VARCHAR(50) NULL COMMENT '创建人', + `create_time` DATETIME NULL COMMENT '创建时间', + `update_by` VARCHAR(50) NULL COMMENT '更新人', + `update_time` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_fa_activity_sch_status` (`sch_id`, `status`, `is_pulled`), + KEY `idx_fa_activity_publisher` (`sch_id`, `publisher_id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '活动主表'; + +CREATE TABLE IF NOT EXISTS `fa_activity_read` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', + `activity_id` INT NOT NULL COMMENT '活动ID', + `class_id` VARCHAR(50) NOT NULL COMMENT '班级ID(已读按班级粒度)', + `student_id` INT NULL COMMENT '首读学生ID(家长=学生)', + `read_time` DATETIME NULL COMMENT '阅读时间', + `create_by` VARCHAR(50) NULL COMMENT '创建人', + `create_time` DATETIME NULL COMMENT '创建时间', + `update_by` VARCHAR(50) NULL COMMENT '更新人', + `update_time` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_fa_read_activity_class` (`activity_id`, `class_id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '活动已读记录(班级粒度)'; + +CREATE TABLE IF NOT EXISTS `fa_activity_signup` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键(前端 signupId)', + `activity_id` INT NOT NULL COMMENT '活动ID', + `sch_id` INT NULL COMMENT '学校ID(数据隔离)', + `student_id` INT NULL COMMENT '学生ID', + `student_name` VARCHAR(50) NULL COMMENT '学生姓名', + `class_id` VARCHAR(50) NULL COMMENT '班级ID', + `class_name` VARCHAR(100) NULL COMMENT '班级名称', + `guardian_name` VARCHAR(50) NULL COMMENT '家长姓名', + `guardian_phone` VARCHAR(20) NULL COMMENT '联系电话', + `join_count` INT NOT NULL DEFAULT 1 COMMENT '参加人数(含学生本人)', + `status` VARCHAR(16) NOT NULL DEFAULT 'none' COMMENT 'none/signed/confirmed/canceled', + `remark` VARCHAR(500) NULL COMMENT '备注(过敏史、随行说明等)', + `signed_at` DATETIME NULL COMMENT '回执时间', + `create_by` VARCHAR(50) NULL COMMENT '创建人', + `create_time` DATETIME NULL COMMENT '创建时间', + `update_by` VARCHAR(50) NULL COMMENT '更新人', + `update_time` DATETIME NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_fa_signup_activity_student` (`activity_id`, `student_id`), + KEY `idx_fa_signup_activity_status` (`activity_id`, `status`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '活动回执/报名记录'; + +CREATE TABLE IF NOT EXISTS `operation_log` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `operator_id` INT NULL COMMENT '操作人平台用户ID', + `operate_time` DATETIME NULL COMMENT '操作时间', + `operator` VARCHAR(64) NULL COMMENT '操作人姓名', + `ip` VARCHAR(64) NULL COMMENT '客户端IP', + `module` VARCHAR(64) NULL COMMENT '模块名称', + `content` VARCHAR(3000) NULL COMMENT '操作内容', + `sch_id` INT NULL COMMENT '学校ID(超管=0)', + `request_uri` VARCHAR(255) NULL COMMENT '请求URI', + `request_method` VARCHAR(16) NULL COMMENT '请求方法', + PRIMARY KEY (`id`), + KEY `idx_oplog_sch_time` (`sch_id`, `operate_time`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '业务操作日志'; diff --git a/src/main/resources/sql/fa_drop_account_use_id.sql b/src/main/resources/sql/fa_drop_account_use_id.sql new file mode 100644 index 0000000..8e9655c --- /dev/null +++ b/src/main/resources/sql/fa_drop_account_use_id.sql @@ -0,0 +1,46 @@ +-- ===================================================================== +-- 活动信息发布模块 · 账号改ID迁移脚本 +-- 变更:实体不再保存教师/学生的登录账号(publisher_account / account / +-- guardian_account / user_account / creator_account / operation_log.account), +-- 统一改存平台用户ID / 学生ID。业务回退查询见各 Repository。 +-- 说明: +-- 1. 新增 ID 列后,历史行无法在本库内把账号映射为平台用户ID(用户表在基础平台库), +-- publisher_id / creator_id 历史行为 NULL;fa_activity_read.student_id、 +-- fa_activity_signup.student_id 本就落了 ID,无需回填。 +-- 2. fa_activity_signup 唯一键由 (activity_id, guardian_account) 改为 +-- (activity_id, student_id)。如库中该唯一键索引名由 Hibernate 自动生成(UK*), +-- 请先 SHOW CREATE TABLE fa_activity_signup 核对名称后替换。 +-- 3. 本脚本可重复执行前请先核对列是否仍存在(MySQL DROP COLUMN 不存在会报错)。 +-- ===================================================================== +USE `family_activity`; + +-- 1) 活动主表:发布人账号 → 发布人ID +ALTER TABLE `fa_activity` + ADD COLUMN `publisher_id` INT NULL COMMENT '发布人ID(平台用户ID;家长端=学生ID)' AFTER `publisher_name`; +ALTER TABLE `fa_activity` DROP COLUMN `publisher_account`; +-- 注:实体未声明 @Index 时 Hibernate 不会建 idx_fa_activity_publisher; +-- 若目标库手工建过该账号索引,请在此先 DROP 后按新列重建。 +ALTER TABLE `fa_activity` + ADD INDEX `idx_fa_activity_publisher` (`sch_id`, `publisher_id`); + +-- 2) 已读记录:操作账号 → 首读学生ID(已读仍按班级粒度去重,学生ID仅溯源) +ALTER TABLE `fa_activity_read` + ADD COLUMN `student_id` INT NULL COMMENT '首读学生ID(家长=学生)' AFTER `class_id`; +ALTER TABLE `fa_activity_read` DROP COLUMN `account`; + +-- 3) 回执/报名记录:删除家长账号(家长=学生,student_id 即身份),唯一键随之迁移 +ALTER TABLE `fa_activity_signup` DROP INDEX `UK68upl3jts93doy45ggqwqdf4o`; +ALTER TABLE `fa_activity_signup` DROP COLUMN `guardian_account`; +ALTER TABLE `fa_activity_signup` + ADD UNIQUE KEY `uk_fa_signup_activity_student` (`activity_id`, `student_id`); + +-- 4) 联系组:创建人账号 → 创建人ID +ALTER TABLE `contacts_group` + ADD COLUMN `creator_id` INT NULL COMMENT '创建人ID(平台用户ID)' AFTER `creator_account`; +ALTER TABLE `contacts_group` DROP COLUMN `creator_account`; + +-- 5) 联系组成员:删除用户账号快照(user_id 即身份) +ALTER TABLE `contacts_group_user` DROP COLUMN `user_account`; + +-- 6) 操作日志:删除操作人账号(operator_id 即身份) +ALTER TABLE `operation_log` DROP COLUMN `account`;