# /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 |