Эх сурвалжийг харах

chore: document blocked visit analytics migration

彭峰 1 сар өмнө
parent
commit
b003d56bbd

+ 1 - 0
docs/migration/admin-migration-status.md

@@ -23,6 +23,7 @@
 - 旧 `GroupAdd/GroupAdd_Submit` 已迁为专用 `saveGroup`:新增组使用 PostgreSQL 事务锁在帐套内原子分配只读 `groupId`,校验同名组、父组归属与层级循环;通用保存不能绕过。删除组前会拒绝仍被用户或下级组引用的记录。
 - 旧 `ContentManage_Status`、`ContentManage_Del`、回收站恢复与 `ContentMove_Submit` 已迁为专用 `contentBatch`:支持每批 1–100 条内容、限定旧状态 `-3/-2/-1/0/99`、校验目标节点帐套并保留 addon 关联。`CommonModel` 的 `generalId/modelId/nodeId/itemId/tableName/status/sourceKey/orderId` 改为通用只读;新增按钮关闭,避免只写主表却遗漏模型附表。
 - 旧 `GetMDFile` 的固定空响应、`Content_API.duptitle` 与 `ContentManage_Refresh` 已由 `legacyContentUtility/contentBatch` 等价迁移;Angular 内容页增加重复标题检查、单条/批量时间刷新和八列 CSV 安全导出。旧 XLSX 格式暂为部分迁移;专题关系、定时发布、静态 HTML、采集和 Markdown 因物理数据或旧运行时缺失均返回明确 `migration_blocked`。
+- `Design/Counter` 与 `Extend/Counter` 共 14 个访问统计动作已完成数据审计:目标全部 Schema 不含旧 `ZL_Com_VisitCount` 或等价访问明细,无法还原 InfoID、日期、地域、浏览器、系统和来源聚合;全站统计代码还缺少 `SiteInfo.AllSiteJS` 注入运行时。对应云函数统一返回细分 `migration_blocked`,不会以 `SysLog` 或问卷 `SurveyLog` 伪造访问统计。
 - 旧 `NodeAdd_Submit`、`Node_API` 与 `UnionNode_Move` 的核心栏目生命周期已迁为专用 `saveNode/nodeBatch`:新增栏目以临时来源键完成 Parse 初始写入,再使用 PostgreSQL 事务锁原子分配 `nodeId/orderId` 并替换为旧 `sourceKey`;保存会校验同级名称/目录重复、父栏目帐套和回收状态,移动会阻断自身/下级循环并递归修正所有后代深度。回收、恢复和每批 1–100 个栏目迁移已接入 Angular;永久删除仅在没有内容、下级栏目、节点权限和模型模板引用时开放,通用保存与删除不能绕过。
 - 旧 `SpecialAdd_Save` 与 `UnionSpecial_Move` 已迁为专用 `saveSpecial/specialBatch`:新增专题使用临时来源键和 PostgreSQL 事务锁原子分配 `specId/orderId`,保存时校验全帐套名称/目录唯一、父专题归属与层级循环;删除会拒绝仍有下级专题的记录。旧 `UnionSpecial_Merge` 被明确列为数据阻塞:`CommonModel.specialId` 只存在于 Parse Schema、PostgreSQL 物理列缺失,商品主表也未迁移,无法安全改写内容/商品专题关系。
 - 旧 `ModelManage/Field/Order_Submit` 的安全部分已迁为 `saveModelMetadata/saveModelFieldMetadata/modelFieldOrder`:现有模型可编辑名称、条目文案、图标、说明和内容模板元数据,现有字段可编辑别名、提示、显示/搜索配置,并可在同模型内批量排序;`modelId/modelType/tableName/fieldId/modelId/fieldName/fieldType/sourceKey` 等结构字段通用只读。新增、复制、删除模型或字段会显式返回 501,因为旧实现同时执行 PostgreSQL 建表/改列、标签生成和模板文件写入,目标托管环境没有该 DDL/旧模板运行时。

+ 18 - 0
scripts/deploy-admin-functions.mjs

@@ -1281,6 +1281,24 @@ async function handler(request, response) {
       if (String(input.action || '') !== 'Index') fail(400, '不支持的旧 Office 图表动作');
       fail(501, 'migration_blocked: 旧 Office/Chart.Index 只保留了返回视图的空壳控制器,交付包与官方源码均缺少对应 Index.cshtml 视图、数据源和交互定义,无法 1:1 复原');
     }
+    if (operation === 'legacyCounterBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        'Design/VisitList': '目标 Schema 缺少旧 ZL_Com_VisitCount 访问明细表及 h5 访问记录',
+        'Design/VisitInfoList': '目标没有可按 InfoID 聚合的 h5 访问明细和最近访问记录',
+        'Design/VisitInfoDetail': '目标缺少指定 InfoID 的访问时间、IP、浏览器、系统、地域和来源明细',
+        'Design/Counter_API': '目标没有可定位并删除的旧访问统计记录',
+        'Extend/StatisticalCode': '目标缺少 SiteInfo.AllSiteJS 配置模型及在所有页面注入统计脚本的运行时',
+        'Extend/StatisticalCode_Submit': '云函数不能仅保存任意全站脚本而没有可信的前端注入、CSP 与版本发布链',
+        'Extend/Index': '目标 Schema 缺少 ZL_Com_VisitCount 数据,无法生成旧访问统计首页',
+        'Extend/Site': '目标缺少全站访问明细与总访问计数',
+        'Extend/Month': '目标缺少可按年月聚合的访问日期明细',
+        'Extend/Year': '目标缺少可按年度和月份聚合的访问日期明细',
+        'Extend/Local': '目标缺少访客地域 Address 明细',
+        'Extend/Browser': '目标缺少访客浏览器标识与访问明细',
+        'Extend/Os': '目标缺少访客操作系统标识与访问明细',
+        'Extend/Channel': '目标缺少访问来源渠道与 Referer 明细'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧访问统计动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
     if (operation === 'legacyChatBlocker') {
       const action = String(input.action || ''); const blockers = {
         Default: '目标 Schema 缺少 ChatMsg 消息表,无法还原用户消息数列表',

+ 14 - 0
scripts/generate-admin-coverage.mjs

@@ -52,6 +52,20 @@ const explicit = new Map([
   ['Content/CatchConfig_Submit', ['blocked-infrastructure', '仅保存采集开关不会产生可执行采集能力,目标也没有旧 SiteConfig 更新链']],
   ['Content/MarkDown', ['blocked-data', '目标 Schema 缺少旧 Content_MarkDown 主表与历史 Markdown 记录']],
   ['Content/MarkDown_Del', ['blocked-data', '目标没有可定位并删除的旧 Content_MarkDown 记录']],
+  ['Design / Counter/VisitList', ['blocked-data', '目标 Schema 缺少旧 ZL_Com_VisitCount 访问明细表及 h5 访问记录']],
+  ['Design / Counter/VisitInfoList', ['blocked-data', '目标没有可按 InfoID 聚合的 h5 访问明细和最近访问记录']],
+  ['Design / Counter/VisitInfoDetail', ['blocked-data', '目标缺少指定 InfoID 的访问时间、IP、浏览器、系统、地域和来源明细']],
+  ['Design / Counter/Counter_API', ['blocked-data', '目标没有可定位并删除的旧访问统计记录']],
+  ['Extend / Counter/StatisticalCode', ['blocked-infrastructure', '目标缺少 SiteInfo.AllSiteJS 配置模型及在所有页面注入统计脚本的运行时']],
+  ['Extend / Counter/StatisticalCode_Submit', ['blocked-infrastructure', '云函数不能仅保存任意全站脚本而没有可信的前端注入、CSP 与版本发布链']],
+  ['Extend / Counter/Index', ['blocked-data', '目标 Schema 缺少 ZL_Com_VisitCount 数据,无法生成旧访问统计首页']],
+  ['Extend / Counter/Site', ['blocked-data', '目标缺少全站访问明细与总访问计数']],
+  ['Extend / Counter/Month', ['blocked-data', '目标缺少可按年月聚合的访问日期明细']],
+  ['Extend / Counter/Year', ['blocked-data', '目标缺少可按年度和月份聚合的访问日期明细']],
+  ['Extend / Counter/Local', ['blocked-data', '目标缺少访客地域 Address 明细']],
+  ['Extend / Counter/Browser', ['blocked-data', '目标缺少访客浏览器标识与访问明细']],
+  ['Extend / Counter/Os', ['blocked-data', '目标缺少访客操作系统标识与访问明细']],
+  ['Extend / Counter/Channel', ['blocked-data', '目标缺少访问来源渠道与 Referer 明细']],
   ['Content / Node/NodeManage', ['implemented', '已由 Node 资源列表、分页与专用结构操作替代']],
   ['Content / Node/NodeSearch', ['implemented', '已覆盖按栏目名称搜索并保持帐套隔离']],
   ['Content / Node/NodeAdd', ['implemented', '已由 Node 动态表单与父节点结构控件替代']],

+ 4 - 0
scripts/smoke-admin-functions.mjs

@@ -635,6 +635,10 @@ try {
     const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyContentBlocker', action }, 501);
     if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧内容动作 ${action} 未返回明确迁移阻塞`);
   }
+  for (const action of ['Design/VisitList','Design/VisitInfoList','Design/VisitInfoDetail','Design/Counter_API','Extend/StatisticalCode','Extend/StatisticalCode_Submit','Extend/Index','Extend/Site','Extend/Month','Extend/Year','Extend/Local','Extend/Browser','Extend/Os','Extend/Channel']) {
+    const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyCounterBlocker', action }, 501);
+    if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧访问统计动作 ${action} 未返回明确迁移阻塞`);
+  }
   await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'contentBatch', className: 'CommonModel', action: 'status', objectIds: [temporaryAppointmentCommonObjectId], status: 99 });
 
   const page = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'list', className: 'CourseAppointment', page: 1, pageSize: 2 });

+ 28 - 28
src/app/admin/legacy-admin-coverage.generated.ts

@@ -1210,99 +1210,99 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "Design / Counter",
     "action": "VisitList",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少旧 ZL_Com_VisitCount 访问明细表及 h5 访问记录"
   },
   {
     "module": "Design / Counter",
     "action": "VisitInfoList",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可按 InfoID 聚合的 h5 访问明细和最近访问记录"
   },
   {
     "module": "Design / Counter",
     "action": "VisitInfoDetail",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少指定 InfoID 的访问时间、IP、浏览器、系统、地域和来源明细"
   },
   {
     "module": "Design / Counter",
     "action": "Counter_API",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有可定位并删除的旧访问统计记录"
   },
   {
     "module": "Extend / Counter",
     "action": "StatisticalCode",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "目标缺少 SiteInfo.AllSiteJS 配置模型及在所有页面注入统计脚本的运行时"
   },
   {
     "module": "Extend / Counter",
     "action": "StatisticalCode_Submit",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "云函数不能仅保存任意全站脚本而没有可信的前端注入、CSP 与版本发布链"
   },
   {
     "module": "Extend / Counter",
     "action": "Index",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少 ZL_Com_VisitCount 数据,无法生成旧访问统计首页"
   },
   {
     "module": "Extend / Counter",
     "action": "Site",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少全站访问明细与总访问计数"
   },
   {
     "module": "Extend / Counter",
     "action": "Month",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少可按年月聚合的访问日期明细"
   },
   {
     "module": "Extend / Counter",
     "action": "Year",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少可按年度和月份聚合的访问日期明细"
   },
   {
     "module": "Extend / Counter",
     "action": "Local",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少访客地域 Address 明细"
   },
   {
     "module": "Extend / Counter",
     "action": "Browser",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少访客浏览器标识与访问明细"
   },
   {
     "module": "Extend / Counter",
     "action": "Os",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少访客操作系统标识与访问明细"
   },
   {
     "module": "Extend / Counter",
     "action": "Channel",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标缺少访问来源渠道与 Referer 明细"
   },
   {
     "module": "Shop / Coupon",