# Angular 管理后台与云函数迁移状态 ## 已确认的迁移基线 - 旧 Web 交付物是 ASP.NET Core 发布包,没有 `.sln`、`.csproj` 与原始 Razor 管理页面源码。 - `ZoomLaCMS.dll` 可恢复 79 个后台控制器、至少 1,042 个公开动作;详细清单见 `legacy-admin-inventory.generated.md`。 - 线上 Parse 应用为 `7pIbDBJmKx_main`,当前可见 109 个 Schema,其中 96 个为 SQL Server 规范化迁移目标类。 - 核心数据已存在:`CommonModel` 92,435、`VocabularyWord` 89,620、`NodeAuth` 5,935、`PracticeRecord` 816、`CourseAppointment` 345、`DailyStudyRecord` 108、`LessonRecord` 82。 - 迁移开始前 `Function` 表为空;本阶段已部署 9 个云函数。 ## 本阶段已实现 - Angular 18 独立管理端路由 `/admin`,包含独立登录、后台布局、概览、动态 Schema 表格、动态编辑表单、分页、搜索、软删除和迁移状态页。 - 后台迁移状态页直接读取线上 `xiaoshu.app.gateway/migration_status`,动态显示已实现 action 数量及完整阻塞原因清单,不再使用前端硬编码阻塞计数。 - 后台“全部数据类”目录由管理员云函数实时返回白名单,所有已开放规范化类均可从界面进入动态列表/编辑页;`_Session`、`Function` 等敏感系统类不进入目录。 - 动态编辑器对 Object/Array 执行 JSON 类型校验,错误格式会阻止保存;Relation、File、GeoPoint 等无法安全通用写入的 Parse 类型由前后端共同设为只读,避免结构化字段被字符串覆盖。 - 动态资源表按 `objectId/name/title/username` 等业务字段稳定优先展示,总页数采用向上取整;只读类使用查看抽屉且不显示保存/删除操作。 - `_User` 详情抽屉已接入专用 `resetPassword` 管理员云函数,执行 8 位最小长度与二次确认;新密码不进入通用资源字段或前端持久化存储。 - `_User` 新建入口已改为专用 `createUser` 管理员云函数:帐套内校验邀请账号,事务锁原子分配 `legacyUserId`,固定默认普通组 1,并初始化旧版余额/经验数据;请求不接受管理员或角色字段,开户密码也不进入通用资源草稿。 - 旧 `User_API` 的锁定、解锁和移动用户组已迁为专用 `userBatch` 管理员云函数:支持每批 1–100 人、强制帐套边界,原子同步 `isDisabled/legacyUserData.State` 或 `legacyGroupId/legacyUserData.GroupID`;停用会撤销全部 Parse 会话,普通管理员不能停用自己或管理其他管理员。旧版账号登录也会拒绝已停用/已删除用户。 - `_User` 的状态、旧用户 ID、旧用户组与旧资料 JSON 已从通用字段编辑中移除;密码重置和用户删除新增管理员目标保护并撤销目标会话,避免绕过专用一致性流程。 - 当前线上 `Group` 表为空;用户组移动只接受当前帐套中真实存在的 `Group.groupId`,界面会明确提示先在“全部数据类”建立用户组,不会把不存在的默认编号伪装成可选组。 - 旧 `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` 改为通用只读;新增按钮关闭,避免只写主表却遗漏模型附表。 - 旧 `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/旧模板运行时。 - 旧 `Guest/MsgList/ReplyGuest/MsgShow/Msg_API` 已迁为 `saveGuestbook/guestbookReply/guestbookBatch`:留言列表支持帐套分页、标题搜索和每批 1–100 条审核/取消审核/回收/恢复;详情可白名单编辑标题与正文,管理员回复继承原分类和父 `gid`,再以事务锁原子分配新 `gid/sourceKey`。永久删除会拒绝仍有回复的留言;普通留言新增继续只走已上线 H5 留言云函数。`gid/parentid/cateid/userid/status/sourceKey/gdate/ip` 均通用只读。 - 旧 `Guest/Bar` 共用的分类新增、编辑、推荐与删除已迁为 `saveGuestCategory/guestCategoryBatch`:新增分类用事务锁原子分配 `cateid/orderId/sourceKey`,`gtype=0` 留言分类固定顶级,`gtype=1` 贴吧分类支持父子结构并阻断循环;同父同类型名称防重,删除前同时检查下级分类、留言与帖子引用。贴吧发帖频率仍依赖旧 `GuestConfig` 文件,版块清空与用户级贴吧授权也尚未开放,因此对应动作保守标为部分实现。 - 旧 `Bar/TieList/TieDetail/TieMove/Tie_API` 的帖子工作流已迁为 `barBatch`:每批 1–100 条支持审核/取消审核、回收/恢复、全局/版块置顶、取消排序、沉底、精华/取消精华和移动贴吧分类;移动会验证目标为同帐套 `gtype=1` 分类。`GuestBar` 在通用资源中改为结构只读,避免直接篡改帖子 ID、作者、父回复、状态、排序和精华标记。旧“勋章”动作依赖未迁移奖章关系表,继续保守标为部分实现而不伪造成功。 - 旧 `BarAuthSet/BarAuthSet_Submit` 经逐浪官方源码模型复核,依赖独立 `ZL_Guest_BarAuth` 用户版块关系(`Uid/BarID/Look/Send/Reply`),并非仅靠 `Guestcate.needLog/postAuth` 即可等价实现;目标 109 个 Parse Schema 中只有 `GuestBar/Guestcate`,没有该权限关系或 `ZL_Guest_Medals` 勋章关系。管理员网关新增 `barAuthorization/barMedal` 显式 501 响应,避免界面或调用方误以为保存成功,并将两项授权动作列入最终数据阻塞清单。 - 旧考试后台 `Question_Class_Manage/AddQuestion_Class/Question_Class_GetChild/Question_Class_Add` 已按官方 `ZL_Exam_Class` 模型迁到目标 `ExamClass`:`saveExamClass` 用 PostgreSQL 帐套事务锁原子分配 `cId/cOrderBy/sourceKey`,校验名称唯一、父分类归属和层级循环;`examClassChildren` 保留旧按父编号取子项能力,删除会拒绝仍有下级分类或 `ExamSysQuestions.pClass` 试题引用的记录。Angular 新增“试题分类”导航和结构表单;旧展开树视觉布局尚未复刻,因此列表动作仍保守标为部分实现。 - `_User`、`CommonModel`、`Node` 与 `Special` 表格已补齐当前页全选和批量工具栏,可直接批量停用/解锁/移动用户组,审核、回收、恢复、移动内容节点,回收、恢复、移动栏目,以及移动专题;翻页或重新查询会清空选择,避免误操作隐藏页记录。 - 迁移状态页已接入从反编译基线自动生成的 1,042 项旧后台动作台账,可按模块、action、原因与状态搜索筛选;当前保守登记为 39 项已实现、31 项部分实现、114 项数据阻塞、189 项基础设施阻塞、669 项待逐项核验。生成器和 `admin-coverage:check` 会阻止台账与源码基线漂移。 - Parse 登录只在浏览器保存当前 `sessionToken`;`masterKey` 只在部署进程中使用。 - 云函数网关强制校验管理员身份、`company` 帐套、类白名单、字段白名单和敏感字段过滤。 - `_Session` 不开放,`Function` 源码不开放通用查询或编辑;用户密码只允许专用重置操作。 - 8 个管理/CMS 函数均验证未认证请求返回 401;临时帐套管理员完成真实只读冒烟测试后已删除。 - 第 9 个 `xiaoshu.app.gateway` 已覆盖旧 H5 源码的全部 91 个 action:28 个已映射,源码清单内 63 个显式返回 501 阻塞原因;动态调用的兼容 action `content_add_zt` 也已映射,`product_add`、`product_upd` 仍显式阻塞,因此线上为 29 个已实现、65 个阻塞项。经典注册/资料更新、课程词库进度、六类余额/积分账本读取、学习次数/生词状态、直属团队/VIP 统计、陪练预约学员分组、15 段复习计划、7 类规范化内容新增/更新、预约状态/课时账本/抗遗忘批量创建及旧版统计公式均已完成;缺主表的商品读取、缺短信校验的密码重置及其他缺副表模型继续按请求明确 501。详见 `wxapp-cloud-action-matrix.md`。 - 旧数据的 `isDeleted` 在部分物理表与 Parse Schema 不一致,把它放入 Parse 查询条件会返回 500;网关改为公司条件查询后在结果侧兼容过滤。 - Angular H5 已开始按页回归切换:账号密码登录、版本、栏目、公开内容、8 类 addon 联表,以及学习记录详情、预约详情、生词本、21 天抗遗忘列表可走云函数;私有读取只在真实 Parse 会话下切换,其余 action 仍由显式迁移路由保留旧端点。 ## SQL 视图 / 函数替代状态 | 旧对象 | 云函数替代 | 状态 | 说明 | |---|---|---|---| | `ZL_StripeHtmlTag`、`ZL_StripeTrimstr` | `cms.content-normalizer` | 已实现 | 保守转换为纯文本,覆盖脚本、样式、实体和空白 | | `ZL_EX_UserView` | `cms.users.extended` | 已实现 | 过滤密码、哈希、会话、Token 与 Secret | | `ZL_Exam_ClassView` | `cms.exams.classes` | 已实现基础投影 | 当前目标类缺少可证明的学校/年级 Pointer 时不伪造关联 | | `ZL_Guest_BarView` | `cms.guest.bar` | 已实现基础投影 | 支持帐套与分页 | | `ZL_SearchView` | `cms.search` | 已实现 | 搜索 `CommonModel` 并清理输出 HTML | | `ZL_User_PlatView` | `cms.users.platform` | 已实现 | 使用合并进 `_User` 的 legacy 平台资料 | | `ZL_User_WXView` | `cms.users.wechat` | 已实现 | 使用合并进 `_User` 的微信资料并过滤密钥 | | `ZL_CartProView` | — | 数据阻塞 | 目标 Schema 无购物车类,且迁移登记没有可用行 | | `ZL_Order_PayedView` | — | 数据阻塞 | 目标 Schema 无订单/支付明细类 | | `ZL_Order_ProSaleView` | — | 数据阻塞 | 目标 Schema 无订单商品明细类 | | `ZL_Order_ShareView` | — | 数据阻塞 | 目标 Schema 无订单分享关系类 | | `ZL_Plat_BlogView` | — | 数据阻塞 | 目标 Schema 无博客正文/回复类 | ## 仍需逐页专用迁移 动态 Schema CRUD 覆盖的是规范化业务数据管理能力,不代表旧后台 1,042 个动作已逐页复刻。考试组卷、微信配置、订单支付、模板编辑、数据库备份、站点集群、PowerShell、文件管理等必须逐项判断: 1. 有目标业务类且仍属当前产品范围:新增专用 Angular 页面和专用云函数。 2. 需要第三方密钥或基础设施权限:取得新系统凭据后再实现,前端不得存储密钥。 3. 依赖未迁移表、IIS、Windows 文件系统或旧 Zoomla 运行时:列入最终无法迁移清单,不以空成功响应代替。 ## 管理员启用前置条件 线上 435 个 `_User` 在核验时没有任何 `isAdmin=true`、`role=admin` 或 `adminRoleKey` 管理员。系统因此默认不可登录管理端。上线前必须由持有 Parse `masterKey` 的部署人员为明确指定的现有用户设置: - `isAdmin: true` - `roles: ["admin"]` - `company: Pointer` 跨帐套管理员才可设置 `adminRoleKey: "super-admin"`;不允许从浏览器执行管理员提权。