# /publicity/13 页面端到端审查 **审查日期**: 2026-08-19 **审查范围**: 前端 → 后端 → DB → 原型 **目标页面**: `/publicity/13` (公示详情页) **审查者**: Claude Code (page-tech-review skill) --- ## 0. 组件定位 (四跳链) | 跳 | 命中 | |---|---| | ① 入口 | `proto/html/项目公示.html:697` `window.location.href = '公示详情.html'` — 公示列表点 item 跳详情 | | ② 路由 | `router/index.js:10` → `{ path: 'publicity/:projectId', name: 'publicity-detail', component: () => import('@/views/portal/PublicityDetail.vue') }` | | ③ 组件 | `ry-vue3/src/views/portal/PublicityDetail.vue` | | ④ 原型 | `proto/html/公示详情.html` (跟实现 1 对 1, 文件名 `公示详情.html` 直接被项目公示列表引用) | **说明**: `/publicity/13` 的 `13` 是 `biz_project.project_id`,实测 DB 中 `project_id=13` 的记录是测试数据 (`project_no='3', project_name='2'`,`invitation_url` 有一个 OSS PDF,`support_letter_url` NULL)。 --- ## 1. 主要功能 + 可见性 ### 1.1 主要功能 | 功能 | 前端入口 | 后端接口 | 数据表 | |---|---|---|---| | 查看公示详情 | `PublicityDetail.vue:355` `load()` | `GET /business/public/project/{projectId}` | `biz_project` | | 立即报名 | `PublicityDetail.vue:398-428` `onSignup()` | `POST /business/executionIntent/signup` | `biz_execution_intent` (RuoYi) | | 表达支持意向 (已登录快速通道) | `PublicityDetail.vue:468-478` `onSupportIntent()` | `POST /business/publicity/supportIntent` | `biz_publicity_support_intent` | | 表达执行意向 (已登录快速通道) | `PublicityDetail.vue:480-490` `onExecutionIntent()` | `POST /business/publicity/executionIntent` | `biz_publicity_execution_intent` | | 表达意向 (匿名 dialog) | `PublicityDetail.vue:195-216` `guestDialog` + `onGuestDialogConfirm()` | 同上 (共用 2 个 POST) | 同上 | | 查重"是否已提交" | `PublicityDetail.vue:437-448` `checkIntentSubmitted()` | `GET /business/publicity/hasIntent?projectId=&phone=&type=` | `biz_publicity_support_intent` / `biz_publicity_execution_intent` | | 分享二维码 | `PublicityDetail.vue:249-263` `QRCode.toDataURL` | — (前端纯计算) | — | 页面不是 CRUD 列表,而是 **单页 detail + 4 个交互按钮**。 ### 1.2 可见性 (三层过滤) | 层 | 来源 | 校验字段 | 备注 | |---|---|---|---| | 前端路由 | `router/index.js:10` | 无 meta.role,所有人均可访问 | `/publicity/:projectId` | | 前端菜单 | — (门户页无菜单) | — | 公示页无菜单,靠顶部导航"项目公示"链入 | | 后端 | `SecurityConfig.java:64` | `.requestMatchers("/business/public/**", "/business/publicity/**", "/business/auth/**", "/business/sms/**").permitAll()` | 公开端点已放行匿名访问,登录后走 token 校验身份 | **不能看到本页面的角色**: 不存在角色隔离——**任何用户/访客**都能看公示详情 + 提交意向。 **报名按钮登录校验**: `PublicityDetail.vue:399-403` 前端拦截 `!loggedIn` → 跳转 `/login`。后端 `executionIntent/signup` 应也有 token 校验 (未实测,从项目其他 executionIntent 接口推断)。 --- ## 2. 筛选项 (filter-form) **本页面无 filter-form**,跳过。 --- ## 3. 工具栏按钮 (toolbar) **本页面无传统 toolbar**,操作按钮挂在右侧悬浮条 (`.detail-actions`),见第 5 章。 --- ## 4. 表格 (el-table) **本页面无表格**,跳过。 --- ## 5. UI 元素映射 (本页面是 detail 视图) ### 5.1 左侧 tab 列表 (`ann-menu`) | UI label | 绑定字段 (entity) | 后端 SQL | 命中表.字段 | 选项来源 | 原型对照 | |---|---|---|---|---|---| | 邀请函 | `ann.invitationUrl` | `BizProjectMapper.xml:41` `` | `biz_project.invitation_url` | — | ❌ 原型只有 1 张图 | | 支持函 | `ann.supportLetterUrl` | 同上 `` | `biz_project.support_letter_url` | — | ❌ 原型没有 | | 通知 | `ann.noticeUrl` | ⚠️ **DB 无该列** | ❌ 永远 undefined | — | ❌ 原型没有 | | 日程 | `ann.scheduleUrl` | ⚠️ **DB 无该列** | ❌ 永远 undefined | — | ❌ 原型没有 | **前端 tab 过滤逻辑** (`PublicityDetail.vue:286`): `TAB_DEFS.filter(t => p[t.urlKey])` — URL 为 undefined/null 即隐藏 tab。所以**通知 / 日程 2 个 tab 永远不会出现**(除非 DB 加列 + mapper 加 mapping)。 ### 5.2 右侧悬浮按钮 | 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | 原型对照 | |---|---|---|---|---|---| | 立即报名 | `onSignup` | `POST /business/executionIntent/signup` | `executionIntentService.signup` (推断) | `biz_execution_intent` (RuoYi) | ✅ 原型也有"立即报名" | | 表达支持意向 | `onSupportIntent` → `doSubmitIntent('support', fields)` | `POST /business/publicity/supportIntent` | `BizPublicityIntentController.doSubmit` (66-139 行) | `biz_publicity_support_intent` 全字段 | ❌ 原型没有 | | 表达执行意向 | `onExecutionIntent` → `doSubmitIntent('execution', fields)` | `POST /business/publicity/executionIntent` | 同上分支 | `biz_publicity_execution_intent` 全字段 | ❌ 原型没有 | | 分享二维码 | `showQr = true` | — (前端 QRCode.toDataURL) | — | — | ✅ 原型也有"分享二维码" | ### 5.3 匿名 dialog (`guestDialog`) | 字段 | 控件 | 绑定字段 | 后端字段 | 选项来源 | 原型对照 | |---|---|---|---|---|---| | 姓名 | `el-input` | `guestDialog.form.name` | `name` | — | ❌ 原型没有 dialog | | 手机号 | `el-input` (maxlength=11) | `guestDialog.form.phone` | `phone` | — | ❌ 原型没有 dialog | | 工作单位 | `el-input` | `guestDialog.form.workUnit` | `work_unit` | — | ❌ 原型没有 dialog | | 部门 | `el-input` | `guestDialog.form.department` | `department` (可空) | — | ❌ 原型没有 dialog | | 职务 | `el-input` | `guestDialog.form.position` | `position` (可空) | — | ❌ 原型没有 dialog | **重要: 提交时**,`doSubmitIntent` 把 `guestDialog.form` 直接传 `payload = { projectId, ...fields }` (`PublicityDetail.vue:524`),**不含 sex / email**。后端 controller (`BizPublicityIntentController.java:79-82`) 校验 projectId/name/phone/workUnit 必填,**不校验 sex/email** — 与设计一致。 ### 5.4 真实落点 - **匿名意向 → 落 `biz_publicity_*_intent` 表**: 即使未登录也写表,后端 `BizPublicityIntentController.java:106-115` 通过 `sysUserMapper.checkPhoneUnique(phone)` 反查 user_id 自动回填,实现"账号自动关联"。 - **已登录意向 → 同表**: 直接 `setUserId(SecurityUtils.getUserId())`。 - **报名 → 落 `biz_execution_intent`** (RuoYi 旧表,非 publicity 两表): 这是旧业务,**与公示匿名意向表语义不同**,报名走 login-bound 表,意向走 public 快照表。 --- ## 6. Java Entity ↔ 数据库表 一致性 ### 6.1 biz_project (实测 2026-08-19) ```sql CREATE TABLE `biz_project` ( `project_id` bigint NOT NULL AUTO_INCREMENT, `project_no` varchar(50) NOT NULL, `project_name` varchar(200) NOT NULL, `project_form` varchar(20), `total_sessions` int DEFAULT 0, `done_sessions` int DEFAULT 0, `todo_sessions` int DEFAULT 0, `total_amount` decimal(15,2) DEFAULT 0, `available_amount` decimal(15,2) DEFAULT 0, `paid_labor_amount` decimal(15,2) DEFAULT 0, `paid_meeting_amount` decimal(15,2) DEFAULT 0, `manage_fee` decimal(15,2) DEFAULT 0, `is_finished` char(1) DEFAULT '0', `is_settled` char(1) DEFAULT 'N', `manager_score` decimal(3,1), `sponsor_admin_user_id` bigint, `sponsor_admin_user_name` varchar(200), `lead_user_id` bigint, `is_bid_project` char(1) DEFAULT 'N', `start_time` datetime, `end_time` datetime, `submit_deadline_days` int DEFAULT 0, `support_contract_url` varchar(500), `execute_contract_url` varchar(500), `invitation_url` varchar(500), `support_letter_url` varchar(500), `publish_url` varchar(500), `create_by` varchar(64), `create_user_id` bigint, `create_time` datetime, `update_by` varchar(64), `update_time` datetime, `sponsor_score` decimal(3,1), PRIMARY KEY (`project_id`), UNIQUE KEY `uk_project_no` (`project_no`), KEY `idx_project_form` (`project_form`), KEY `idx_is_finished` (`is_finished`), KEY `idx_is_settled` (`is_settled`), KEY `idx_sponsor_admin_user_id` (`sponsor_admin_user_id`), KEY `idx_start_end_time` (`start_time`,`end_time`) ) ENGINE=InnoDB AUTO_INCREMENT=14; ``` ### 6.2 biz_publicity_support_intent + biz_publicity_execution_intent (同构) ```sql CREATE TABLE `biz_publicity_support_intent` ( `intent_id` bigint NOT NULL AUTO_INCREMENT, `project_id` bigint, `project_no` varchar(64), `project_name` varchar(255), `user_id` bigint, `name` varchar(50), `phone` varchar(20), `work_unit` varchar(200), `department` varchar(100), `position` varchar(100), `source` varchar(32) DEFAULT 'publicity', `intent_status` varchar(32) DEFAULT '待审核', `remark` varchar(500), `create_by` varchar(64), `create_time` datetime, `update_by` varchar(64), `update_time` datetime, PRIMARY KEY (`intent_id`), KEY `idx_project_phone` (`project_id`,`phone`,`source`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2; -- biz_publicity_execution_intent 结构完全一致 ``` ### 6.3 Entity ↔ DB 字段对照 (biz_publicity_support_intent) | 实体字段 | 中文列名 (DB COMMENT) | Java 类型 | 表字段 | DB 类型 | 一致? | |---|---|---|---|---|---| | `intentId` | 意向ID | `Long` | `intent_id` | `bigint AUTO_INCREMENT` | ✅ | | `projectId` | 项目ID | `Long` | `project_id` | `bigint` | ✅ | | `projectNo` | 项目编号(冗余) | `String` | `project_no` | `varchar(64)` | ✅ | | `projectName` | 项目名称(冗余) | `String` | `project_name` | `varchar(255)` | ✅ | | `userId` | 用户ID(按phone关联) | `Long` | `user_id` | `bigint` | ✅ | | `name` | 姓名 | `String` | `name` | `varchar(50)` | ✅ | | `phone` | 手机号 | `String` | `phone` | `varchar(20)` | ✅ | | `workUnit` | 工作单位 | `String` | `work_unit` | `varchar(200)` | ✅ | | `department` | 部门 | `String` | `department` | `varchar(100)` | ✅ | | `position` | 职务 | `String` | `position` | `varchar(100)` | ✅ | | `source` | 来源 publicity=公示页 | `String` | `source` | `varchar(32) DEFAULT 'publicity'` | ✅ | | `intentStatus` | 状态 待审核/已通过/已拒绝 | `String` | `intent_status` | `varchar(32) DEFAULT '待审核'` | ✅ (DB 注释用"已通过",entity 注释用"已采纳",**注释不一致但代码用"已通过"**) | | `remark` | 备注 | `String` | `remark` | `varchar(500)` | ✅ | | `createBy` (BaseEntity) | 创建者 | `String` | `create_by` | `varchar(64)` | ✅ | | `createTime` | 创建时间 | `Date` | `create_time` | `datetime` | ✅ | | `updateBy` (BaseEntity) | 更新者 | `String` | `update_by` | `varchar(64)` | ✅ | | `updateTime` | 更新时间 | `Date` | `update_time` | `datetime` | ✅ | **BizPublicityExecutionIntent 同构**。 ### 6.4 ⚠️ **biz_project Entity ↔ DB 一处不一致 (P0 级)** `BizProject.java:119, 121` 有 `private String noticeUrl;` 和 `private String scheduleUrl;` 两个字段,且 mapper XML **未定义** `` 映射这两个字段 (`BizProjectMapper.xml:40-43` 只 mapping 到 publishUrl): ```xml ``` **结论**: 1. `SELECT` 不会填这 2 个字段,前端拿到 `undefined`,tab 自动隐藏 (`PublicityDetail.vue:286` 过滤) — **不崩,但功能缺失** 2. `INSERT / UPDATE` 也不会写这 2 个字段 (`BizProjectMapper.xml:222-277` 没有 `` 块),即使管理员在 UI 上填了值也写不进 DB 3. DB `biz_project` 实测**没有 `notice_url` / `schedule_url` 这 2 列** **前端 PublicityDetail.vue `TAB_DEFS`** (`PublicityDetail.vue:270-275`) 期望 `ann.noticeUrl` / `ann.scheduleUrl` 字段 — **永远为 undefined**,2 个 tab 永久隐藏。 --- ## 7. 索引检查 ### 7.1 biz_project 索引 | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `project_id` | ✅ `WHERE project_id = ?` (本页面 `getById(13)`) | | uk_project_no | `project_no` (UNIQUE) | ✅ 项目编号唯一性 | | idx_project_form | `project_form` | ✅ 项目形式筛选 | | idx_is_finished | `is_finished` | ✅ 已结题筛选 | | idx_is_settled | `is_settled` | ✅ 已结算筛选 | | idx_sponsor_admin_user_id | `sponsor_admin_user_id` | ✅ 赞助方负责人反查 | | idx_start_end_time | `start_time`,`end_time` | ✅ 时间段查询 | **评价**: `getById(13)` 走 PRIMARY,✅。其它筛选用 idx 系列列,✅。 ### 7.2 biz_publicity_support_intent / execution_intent 索引 | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `intent_id` | ✅ | | idx_project_phone | `project_id`,`phone`,`source` (复合) | ✅ `hasIntent(projectId, phone, 'publicity')` 完全命中 (controller:147-159) | | idx_user_id | `user_id` | ✅ 已登录用户反查 | **评价**: 复合索引 `(project_id, phone, source)` 完美覆盖 controller 的查重 SQL,✅。 --- ## 8. 与原型差异 原型: `proto/html/公示详情.html` 实现: `ry-vue3/src/views/portal/PublicityDetail.vue` ### 8.1 实现新增 (超出原型) | 项 | 实现 | 原型 | 评价 | |---|---|---|---| | 4 个 tab (邀请函/支持函/通知/日程) | ✅ | ❌ 原型只有 1 张图 | 改进 | | 表达支持意向按钮 + 匿名 dialog | ✅ | ❌ 原型没有 | 改进 (与 [publicity-intent-design](publicity-intent-design.md) 记忆一致) | | 表达执行意向按钮 | ✅ | ❌ 原型没有 | 改进 | | 报名按钮已有"已报名"灰显状态 | ✅ | ❌ 原型跳登录 | 改进 | | 顶部 nav 用户态 (登录/头像下拉) | ✅ | ❌ 原型只有"登录"链接 | 改进 | | 加载骨架屏 | ✅ (`main-skeleton` 4 个 tab skeleton) | ❌ | 改进 | | OSS PDF 走 `/common/oss/proxy` (inline 渲染) | ✅ (`proxyUrl` 309-316) | — | **必须** — OSS bucket 设了 attachment 否则 iframe 强制下载 | ### 8.2 原型有但实现缺失 | 项 | 原型 | 实现 | 严重度 | |---|---|---|---| | 静态大图 (邀请函红头文件 letter 样式 + 公章) | ✅ `.letter` + `.seal` (200-340 行) | ⚠️ 仅渲染原始 PDF/图片,无 letter 容器包装 | P2 (视觉降级,但功能等价) | ### 8.3 文字 / 标签差异 | 项 | 原型 | 实现 | 一致? | |---|---|---|---| | 主按钮文字 | "立即报名" | "立即报名" | ✅ | | 二级按钮 | "分享二维码" | "分享二维码" | ✅ | | 顶部 nav 高亮 | "项目公示" | "项目公示" | ✅ | | 主标题 | 无 (`
` 自身有标题) | 无 (用面包屑) | ✅ | ### 8.4 总结 **整体方向: 实现远超原型** (4 tab + 2 新功能按钮 + 骨架屏 + 用户态 + OSS 代理)。原型是 v1 静态图,实现是 v2 完整交互页,改进方向正确。 --- ## 9. 字段冗余 / 设计问题 ### 9.1 biz_publicity_support_intent / execution_intent - **冗余字段**: `project_no`, `project_name`, `user_id` 都是 `biz_project` JOIN 出来的快照字段 — **合理** (匿名提交时项目名/编号可能改名,留快照保证审计追溯)。 - **唯一约束**: `(project_id, phone, source)` 通过 idx_project_phone 复合索引实现查重。**未加 UNIQUE 约束**,纯靠 service 层 `findDuplicate` — 行数大后并发提交可能绕过 (race condition),但匿名提交频率低,暂可接受。 ### 9.2 biz_project 通知/日程字段 - `BizProject.java` 有 `noticeUrl` / `scheduleUrl` 但 DB 没列,mapper 没 mapping → **死代码**。建议二选一: - A. 加 `ALTER TABLE biz_project ADD COLUMN notice_url varchar(500), ADD COLUMN schedule_url varchar(500)` + mapper 补 `` 和 `` 块 - B. 删 entity 这 2 字段 + 前端 TAB_DEFS 删通知/日程 ### 9.3 报名 vs 意向两套表语义重叠 - `biz_execution_intent` (RuoYi 旧表,报名按钮) + `biz_publicity_execution_intent` (公示匿名意向) — 都是"对项目表达执行意向" - `BizSupportIntentController.java` (RuoYi 旧) + `BizPublicityIntentController.java` (新) — 两套 controller - 旧表语义: "已登录 + 报名+ 关联 bid" — 强约束 - 新表语义: "匿名/已登录 + 仅意向快照 + 不进入执行流程" — 弱约束 - **两表并存是设计意图** ([publicity-intent-design](publicity-intent-design.md) 记忆:不复用登录视角的强绑表) — 合理,不冗余 ### 9.4 业务闭环 - 匿名提交 → 反查 sys_user → 自动回填 user_id (`BizPublicityIntentController.java:106-115`) — 闭环 ✅ - 已登录但未维护手机号 → userId 仍能从 SecurityContext 取 (`SecurityUtils.getUserId()`) — 闭环 ✅ - phone 不匹配任何 sys_user → user_id 仍 NULL → manager 端列表"账号状态=不存在" 标签 (见 manager/SupportIntent.vue) — 闭环 ✅ ### 9.5 数据隔离 - 公开端点 `/business/publicity/**` permitAll (匿名可访问) - 管理端点 `/business/publicitySupportIntent/**` 受 token 保护 (`SecurityConfig.java` 无显式 permitAll) - **匿名提交的数据隔离靠 source='publicity' 字段** — manager 端 list 自动按 source 过滤 (推断,需进一步看 mapper XML 验证) --- ## 10. 待修复列表 | # | 问题 | 文件 | 修复建议 | 严重度 | |---|---|---|---|---| | 1 | `BizProject.noticeUrl` / `scheduleUrl` 字段无 DB 列、无 mapper mapping,**死代码** | `BizProject.java:119,121` + `BizProjectMapper.xml` + `PublicityDetail.vue:270-275` | 二选一:A) `ALTER TABLE biz_project ADD notice_url/schedule_url` + mapper 补 mapping;B) 删 entity 字段 + 前端 TAB_DEFS 删 通知/日程 | P0 | | 2 | `intent_status` 中文注释 entity 写"已采纳",DB 写"已通过",代码实际用"已通过" (`changeStatus(row, '已通过')` SupportIntent.vue:57) | `BizPublicitySupportIntent.java:48` | 改 entity 注释为"已通过" | P3 | | 3 | 邀请函原文渲染: 原型 letter 容器样式 + 公章位置 (`.letter` + `.seal`) 未实现,直接渲染 PDF/图片丢视觉 | `PublicityDetail.vue:73-90` (content 容器) | 加 letter 容器样式 + 公章绝对定位 (与 manager/Publicity 项目详情一致) | P2 | | 4 | 匿名提交**无反爬限制** (`/business/publicity/supportIntent` permitAll),同 IP 可无限提交不同 phone | `BizPublicityIntentController.java:50-54` | 加 rate-limit (按 IP/phone/项目 5 分钟内 1 次) | P2 | | 5 | `findDuplicate` 复合唯一性靠 service 层 SELECT,无 DB UNIQUE KEY — 高并发下可能 race condition 重复插入 | `biz_publicity_support_intent` 表 | 加 `UNIQUE KEY uk_proj_phone_source (project_id, phone, source)` | P3 (匿名低频,暂可接受) | | 6 | 前端 tab 默认 active 取"第一个有 URL 的 tab",如果 4 个 tab 都没 URL (全部 null),`activeTab` 仍可能是空字符串 → 用户看不到任何内容 | `PublicityDetail.vue:357-361` | 加兜底:全无 URL 时显示"暂无可查看文件"占位 | P2 | | 7 | 公示页 iframe 加载 OSS PDF 时,**没有 loading 指示器** — 用户看到白屏几秒 | `PublicityDetail.vue:74-78` | 给 iframe 加 `@load` 隐藏骨架屏,或 PDF.js 内嵌渲染 | P3 | | 8 | "已支持" / "已表达意向" 状态依赖前端 `checkIntentSubmitted()` 用本地 phone 查重,**清缓存后状态丢失**(重新查接口,但有 200ms 延迟用户看到 "支持" 闪烁) | `PublicityDetail.vue:431-453` | 用 `sessionStorage` 缓存"已提交"标志,onMounted 时优先读缓存再异步验证 | P3 | --- ## 11. 引用清单 | 文件 | 行号 | 说明 | |---|---|---| | `ry-vue3/src/views/portal/PublicityDetail.vue` | 全文 | 审查目标 | | `ry-vue3/src/views/portal/PublicityDetail.vue:195-216` | — | 匿名意向 dialog (本次已加 guest-intent-form 样式) | | `ry-vue3/src/views/portal/PublicityDetail.vue:270-275` | — | TAB_DEFS (4 tab 定义) | | `ry-vue3/src/views/portal/PublicityDetail.vue:309-316` | — | `proxyUrl` OSS 代理 | | `ry-vue3/src/views/portal/PublicityDetail.vue:355-370` | — | `load()` 调 GET /business/public/project/{projectId} | | `ry-vue3/src/views/portal/PublicityDetail.vue:398-428` | — | `onSignup()` 立即报名 | | `ry-vue3/src/views/portal/PublicityDetail.vue:437-453` | — | `checkIntentSubmitted` 查重 | | `ry-vue3/src/views/portal/PublicityDetail.vue:468-490` | — | 支持/执行意向入口 (登录分支) | | `ry-vue3/src/router/index.js` | 10 | `/publicity/:projectId` 路由 | | `proto/html/项目公示.html` | 697 | 列表 → 详情跳转 | | `proto/html/公示详情.html` | 全文 | 原型基准 | | `ry-vue3/src/api/public.js` | 117-127 | publicity intent 4 个公开端点 | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicController.java` | 91-94 | GET /business/public/project/{projectId} | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicityIntentController.java` | 全文 | 公示页意向 controller (公开 + 管理双端点) | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicityIntentController.java:66-139` | — | `doSubmit` 通用提交逻辑 | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicityIntentController.java:106-115` | — | 匿名 → 反查 sys_user → 回填 user_id | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java` | 113, 115, 119, 121 | 4 个 URL 字段 (含**死代码** noticeUrl/scheduleUrl) | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPublicitySupportIntent.java` | 全文 | 公示-支持意向 entity | | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPublicityExecutionIntent.java` | 全文 | 公示-执行意向 entity | | `ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml` | 40-43, 67-69 | URL 字段的 `` 和 SELECT (无 notice/schedule) | | `ry-api/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java` | 64 | 公开端点放行 (`/business/public/**`, `/business/publicity/**`) | | MySQL `guoju0808.biz_project` | — | 32 列 + 1 主键 + 1 唯一 + 5 索引,实测 2026-08-19 | | MySQL `guoju0808.biz_publicity_support_intent` | — | 17 列 + 1 主键 + 2 索引,实测 2026-08-19 | | MySQL `guoju0808.biz_publicity_execution_intent` | — | 同上,实测 2026-08-19 | | MySQL `guoju0808.biz_project.project_id=13` | — | 测试数据, invitation_url 有 OSS PDF,其他 URL 全 NULL |