-- ===================================================================== -- 办事大厅(svc-hall)数据迁移脚本:CMS cms_service_item/cms_service_dict -- → bsch_svchall.service_item / service_dict / service_attachment -- -- ⚠️ 交付物,未执行(遵守"SQL 交付不代跑"约定)。执行前必须: -- 1. 核对/修改 @cms_public_base(CMS 对外基址=域名根,见下方 ① 的取值规则; -- 配错会全量断图——②④ 拼接处自带 /cms/upload/ 段,base 再含 /cms 即双段); -- 2. 目标库 bsch_svchall 已由应用首次启动(ddl-auto=update)建好六表结构; -- 3. 幂等性:可重复执行(先 DELETE 再 INSERT,MySQL 会话变量需在同一连接内); -- 4. 回滚 = CMS 侧把 cms.svc.mode 切回 local 前的 git revert(CMS 库数据全程未改不删除, -- 本脚本只从 CMS 读、向办事大厅写)。 -- -- 预检(执行前人工跑,结果非空先补配置再迁移): -- 迁移前查 CMS 侧未配学校的站点清单(这些站点的数据将落到 sch_id=0 孤儿租户, -- 管理端/门户均不可见,见 6.2 校验): -- SELECT id, code, name FROM bsch_cms.cms_site WHERE sch_id IS NULL; -- -- 对应方案:《办事大厅独立应用与对接方案V2.0.md》Part E.2; -- 字段语义见 docs/19 §九 与 V2.0 Part B.2。 -- ===================================================================== -- ① 会话变量:CMS 对外基址=域名根(不含 context-path、末尾不带斜杠)。 -- 取值规则:② 富文本 REPLACE 与 ④ 附件 CONCAT 在 base 之后拼的是库内既有形态 -- `/cms/upload/...`(CMS FILE_WEB_PATH=/cms/upload/ 已含 context-path), -- 故 base 必须是域名根——含 /cms 会产出 `.../cms/cms/upload/...` 双段 URL 全量断图。 -- 本地示例:http://localhost:8072 ;生产示例:https://base.yunhaixingtong.com SET @cms_public_base := 'https://base.yunhaixingtong.com'; -- ② 事项(主键 id 原值保留——CMS 门户 /sv/{id} 路由直接携带办事大厅主键,V2.0 C.2"id 语义") DELETE FROM `bsch_svchall`.`service_item` WHERE id <= 2100000000; INSERT INTO `bsch_svchall`.`service_item` (id, sch_id, site_code, item_code, title, summary, object_key, category_key, dept_group_id, dept_name, handle_mode, online_flag, handle_place, handle_time_limit, promise_time_limit, contact_phone, supervise_phone, process_html, material_html, notice_html, faq_category_id, notice_category_id, rule_category_id, cover_image, form_code, flow_guid, apply_url, sort_no, status, publish_time, offline_time, view_count, ext_json, create_by, create_time, update_by, update_time) SELECT s.id, COALESCE(site.sch_id, 0), site.code, s.item_code, s.title, s.summary, s.object_key, s.category_key, s.dept_group_id, s.dept_name, s.handle_mode, s.online_flag, s.handle_place, s.handle_time_limit, s.promise_time_limit, s.contact_phone, s.supervise_phone, -- 富文本内链绝对化(一次性 REPLACE:相对 /cms/upload/ → CMS 绝对地址;文件物理不动)。 -- 取值规则:库内相对形态已含 context-path(FILE_WEB_PATH=/cms/upload/),故此处 -- CONCAT 只补域名根(@cms_public_base 不含 /cms,否则产出 /cms/cms/ 双段断图) REPLACE(REPLACE(s.process_html, 'src="/cms/upload/', CONCAT('src="', @cms_public_base, '/cms/upload/')), 'href="/cms/upload/', CONCAT('href="', @cms_public_base, '/cms/upload/')), REPLACE(REPLACE(s.material_html, 'src="/cms/upload/', CONCAT('src="', @cms_public_base, '/cms/upload/')), 'href="/cms/upload/', CONCAT('href="', @cms_public_base, '/cms/upload/')), REPLACE(REPLACE(s.notice_html, 'src="/cms/upload/', CONCAT('src="', @cms_public_base, '/cms/upload/')), 'href="/cms/upload/', CONCAT('href="', @cms_public_base, '/cms/upload/')), s.faq_category_id, s.notice_category_id, s.rule_category_id, -- 封面:相对路径 → CMS 绝对地址(已是 http(s) 的外链不受影响;相对形态 /cms/upload/ 自带段) CASE WHEN s.cover_image LIKE 'http%' THEN s.cover_image WHEN s.cover_image LIKE '/cms/upload/%' THEN CONCAT(@cms_public_base, s.cover_image) ELSE s.cover_image END, NULL, -- form_code:迁移数据无表单,运营后续在办事大厅配置 NULL, -- flow_guid:迁移数据不绑流程;online_flag 需同步降级(见下方 UPDATE) JSON_UNQUOTE(JSON_EXTRACT(s.ext_json, '$.apply_url')), s.sort_no, s.status, s.publish_time, s.offline_time, s.view_count, s.ext_json, s.create_by, s.create_time, s.update_by, s.update_time FROM `bsch_cms`.`cms_service_item` s JOIN `bsch_cms`.`cms_site` site ON site.id = s.site_id; -- 一致性收敛(V2.0 B.2.1:online_flag=1 ⇒ flow_guid 非空): -- 存量数据一律不绑流程,先全部降为"不可在线办理",由运营在办事大厅逐事项绑定流程后开启。 UPDATE `bsch_svchall`.`service_item` SET online_flag = 0 WHERE flow_guid IS NULL; -- ③ 字典 DELETE FROM `bsch_svchall`.`service_dict` WHERE id <= 2100000000; INSERT INTO `bsch_svchall`.`service_dict` (id, sch_id, site_code, dict_type, dict_key, dict_name, sort_no, status, ext_json, create_by, create_time, update_by, update_time) SELECT d.id, COALESCE(site.sch_id, 0), site.code, d.dict_type, d.dict_key, d.dict_name, d.sort_no, d.status, d.ext_json, d.create_by, d.create_time, d.update_by, d.update_time FROM `bsch_cms`.`cms_service_dict` d JOIN `bsch_cms`.`cms_site` site ON site.id = d.site_id; -- ④ 指南附件(仅 svc_item 显式列表场景;svc_item_asset 为内容派生归属, -- 新模型下封面/正文图只存 URL 不建行,故不迁移) DELETE FROM `bsch_svchall`.`service_attachment` WHERE id <= 2100000000; INSERT INTO `bsch_svchall`.`service_attachment` (id, sch_id, topic_type, topic_id, field_key, file_name, file_url, file_size, mime_type, create_by, create_time, update_by, update_time) SELECT a.id, COALESCE(site.sch_id, 0), 'svc_item_guide', a.ref_id, NULL, a.file_name, -- web_url 相对地址 → CMS 绝对地址(割接期文件物理在 CMS 卷,URL 保持可访问;长期归属见 V2.0 R3)。 -- 取值规则:web_url 库内形态=/cms/upload/...(已含 context-path),故只补域名根 CONCAT(@cms_public_base, a.web_url), a.file_size, a.mime_type, a.create_by, a.create_time, a.update_by, a.update_time FROM `bsch_cms`.`cms_attachment` a JOIN `bsch_cms`.`cms_site` site ON site.id = a.site_id WHERE a.ref_type = 'svc_item' AND a.ref_id IS NOT NULL; -- ⑤ 自增起点收敛(保证迁移 id 之后的新增不冲突;执行前需确认 max) SET @max_item_id := (SELECT COALESCE(MAX(id), 0) + 10 FROM `bsch_svchall`.`service_item`); SET @sql := CONCAT('ALTER TABLE `bsch_svchall`.`service_item` AUTO_INCREMENT = ', @max_item_id); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ⑥ 迁移校验(人工核对,任何一处不符先停) -- 6.1 行数一致: -- SELECT (SELECT COUNT(*) FROM bsch_cms.cms_service_item) AS src, -- (SELECT COUNT(*) FROM bsch_svchall.service_item) AS dst; -- 6.2 无孤儿租户(P1-2:三处 COALESCE(site.sch_id,0) 使 NULL 永不出现, -- 未配学校的站点落到 sch_id=0——不属任何租户,管理端/门户均不可见。 -- 结果非 0 = 预检漏了未配学校站点,回 CMS 补 sch_id 后重迁): -- SELECT COUNT(*) FROM bsch_svchall.service_item WHERE sch_id = 0 OR site_code IS NULL; -- SELECT COUNT(*) FROM bsch_svchall.service_dict WHERE sch_id = 0; -- SELECT COUNT(*) FROM bsch_svchall.service_attachment WHERE sch_id = 0; -- 6.3 字典行数一致(同上两张表); -- 6.4 附件行数 = CMS 侧 ref_type='svc_item' 且 ref_id 非空的行数; -- 6.5 抽一条事项走办事大厅 oapi:GET /oapi/svc/item/detail?siteCode=&itemCode=, -- 核对三段富文本图片 URL 均为绝对地址且可访问。