diff --git a/.cursor/agent/开发助理/项目索引/小程序.md b/.cursor/agent/开发助理/项目索引/小程序.md
index 2fc93a88..2a4a6011 100644
--- a/.cursor/agent/开发助理/项目索引/小程序.md
+++ b/.cursor/agent/开发助理/项目索引/小程序.md
@@ -52,9 +52,10 @@
| 2026-04-13 | 会议:按功能同步开发文档;页面级记 miniprogram 接口与分享行为;无接口变更亦标注 | 进行中 |
| 2026-04-14 | 吸收沉淀:逆推闭环自检写入 evolution;LOOP 需求行 | 进行中 |
| 2026-04-15 | 业务补齐:`app.js` 挂 `err.response`;导师详情微信支付;找伙伴 join 失败展示后端文案 | 已完成 |
+| 2026-05-09 | 我的页快捷入口:渐变面性 SVG 图标、`stat-icon-wrap` 圆形底托与卡片光效;开发文档运营与变更 / 当前小程序开发细则 §2.2.1 已同步 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
---
-**最后更新**:2026-04-15
+**最后更新**:2026-05-09
diff --git a/.cursor/skills/assistant-doc-sync/sync-log.md b/.cursor/skills/assistant-doc-sync/sync-log.md
index 6454e908..75a49ad9 100644
--- a/.cursor/skills/assistant-doc-sync/sync-log.md
+++ b/.cursor/skills/assistant-doc-sync/sync-log.md
@@ -6,6 +6,10 @@
---
+## 2026-05-09
+
+- 文档同步:小程序「我的」快捷入口 UI 变更写入 `开发文档/10、项目管理/运营与变更.md`、`开发文档/4、前端/当前小程序开发细则.md` §2.2.1;`项目索引/小程序.md` 开发进度追加
+
## 2026-04-15
- 吸收经验(联网):编程思维沉淀至 `agent/团队/evolution/2026-04-15.md`;升级 `role-flow-control` §1.3;`经验清单`、总索引、团队/助理橙子项目索引、sync-log 已更新
diff --git a/.cursor/skills/change-checklist/SKILL.md b/.cursor/skills/change-checklist/SKILL.md
index b8b1de7a..70579d9f 100644
--- a/.cursor/skills/change-checklist/SKILL.md
+++ b/.cursor/skills/change-checklist/SKILL.md
@@ -77,7 +77,7 @@ description: Soul 创业派对变更关联检查。miniprogram/soul-admin/soul-a
| 找伙伴/匹配 | 匹配、展示 | match、ckb 等 miniprogram 接口 | 匹配配置、开关 |
| 配置项 | 仅读取 | miniprogram/config 或 db/config | 配置编辑(admin/db) |
| VIP/超级个体 | 展示列表、详情 | miniprogram/vip/members;db/users PUT、db/vip-roles | 用户列表「设置 VIP」弹窗、VIP 角色管理页 |
-| 超级个体发文(UGC+AI) | `super-article-editor`、`super-article-detail` | 表 `super_articles`;`GET/POST` `/api/miniprogram/super/articles*`、`/super/articles/generate` | 当前无独立超管文章列表为常态时勿只挂 admin;**勿**与 book/章节阅读接口混改或合并列表 |
+| 超级个体发文(UGC+AI) | `super-article-editor`、阅读页 **`read`(`superArticleId`)**、`super-article-detail`(兼容重定向)、`super-article-mine` 等 | 表 `super_articles`;`GET/POST/DELETE` `/api/miniprogram/super/articles*`、`/super/articles/generate` | 当前无独立超管文章列表为常态时勿只挂 admin;**勿**与 book/章节阅读接口混改或合并列表;详情 UI 与章节共用 `read` 但 **数据模型仍隔离** |
**专项检查**:若本次变更涉及 `super_articles`、**`super-article-*` 页面** 或 `super/articles` 接口,再读 [super-individual-article/SKILL.md](../super-individual-article/SKILL.md),确认未与书籍/章节、MBTI 的 `soul_articles` 混表或混接口。
diff --git a/.cursor/skills/miniprogram-dev/SKILL.md b/.cursor/skills/miniprogram-dev/SKILL.md
index e7ac2ac6..4e027cca 100644
--- a/.cursor/skills/miniprogram-dev/SKILL.md
+++ b/.cursor/skills/miniprogram-dev/SKILL.md
@@ -47,7 +47,7 @@ description: Soul 创业派对小程序开发规范。在 miniprogram/ 下编辑
- **支付**:下单/查单走 `POST/GET /api/miniprogram/pay`、回调由 soul-api 处理;支付前必须已有 openId(通过登录或 getOpenId 获得)。
- **提现**:申请/记录/确认等走 `/api/miniprogram/withdraw/*`;订阅消息模板 ID 在 `app.globalData.withdrawSubscribeTmplId`。
- **scene 编解码**:海报与扫码统一用 `utils/scene.js` 的 `buildScene`/`parseScene`,支持 mid、id、ref,分隔符与后端生成一致(如 `_`)。
-- **超级个体 AI 写文章**:数据落在 soul-api 表 `super_articles`,接口为 `/api/miniprogram/super/articles`(生成/发布/列表/详情),页面 `super-article-editor`、`super-article-detail`;**勿与书籍/章节阅读流混为同一数据模型或接口**。详见 [super-individual-article/SKILL.md](../super-individual-article/SKILL.md)。
+- **超级个体 AI 写文章**:数据落在 soul-api 表 `super_articles`,接口为 `/api/miniprogram/super/articles`(生成/发布/列表/详情/删除);编辑器 `super-article-editor`;**正文详情展示与书籍章节共用 `pages/read/read`,通过 query `superArticleId`(或 `saId`)进入动态模式**;`super-article-detail` 仅保留对 `read` 的兼容重定向。**数据模型与书籍章节接口仍分离**,勿混表混列表。详见 [super-individual-article/SKILL.md](../super-individual-article/SKILL.md)。
---
@@ -115,6 +115,6 @@ description: Soul 创业派对小程序开发规范。在 miniprogram/ 下编辑
- 做个人中心、设置页布局时(遵循 §7,卡片区边距 16rpx)。
- 做阅读、文章等需长按复制的文本时(遵循 §9,text 加 user-select)。
- 做编辑资料页分享名片时(遵循 §10)。
-- 做超级个体 **AI 写文章**、`super-article-editor` / `super-article-detail` 时(遵循 §4 最后一条与 [super-individual-article/SKILL.md](../super-individual-article/SKILL.md))。
+- 做超级个体 **AI 写文章**、`super-article-editor`、**`read`(含 `superArticleId` 动态详情)** / `super-article-detail`(兼容跳转)时(遵循 §4 超级个体一条与 [super-individual-article/SKILL.md](../super-individual-article/SKILL.md))。
遵循本 Skill 可保证小程序只与 soul-api 的 miniprogram 路由组对接,避免与管理端或 next-project 接口混用。
diff --git a/.cursor/skills/super-individual-article/SKILL.md b/.cursor/skills/super-individual-article/SKILL.md
index 1289c1e7..15533077 100644
--- a/.cursor/skills/super-individual-article/SKILL.md
+++ b/.cursor/skills/super-individual-article/SKILL.md
@@ -44,7 +44,7 @@ description: 超级个体 AI 写文章。super_articles 表、/api/miniprogram/s
| 方法 | 路径 | 说明 |
|------|------|------|
-| POST | `/api/miniprogram/super/articles/generate` | 根据描述 + 可选图片 URL 调用大模型,返回 `title`/`content` 草稿;需配置 OpenAI 兼容的 Key/模型 |
+| POST | `/api/miniprogram/super/articles/generate` | 提示词 `description` + 可选 `referenceText`(端上文本文件拼接)+ `materialUrls`(附件上传 URL,服务端安全域内抓取)+ `imageUrls`;四者至少一项;返回 `title`/`content` 草稿;需 OpenAI 兼容 Key/模型;契约见 `开发文档/5、接口/API接口完整文档.md` §1.3 |
| POST | `/api/miniprogram/super/articles` | 发布;body:`userId`, `title`, `content`;失败时可能带 `forbidden: true` |
| GET | `/api/miniprogram/super/articles?authorUserId=&page=&pageSize=` | 作者为超级个体时返回 ta 的文章列表(含 preview) |
| GET | `/api/miniprogram/super/articles/:id` | 详情(作者须为超级个体,否则不可见) |
@@ -57,11 +57,14 @@ description: 超级个体 AI 写文章。super_articles 表、/api/miniprogram/s
| 页面/逻辑 | 路径 |
|-----------|------|
-| AI 写文 + 发布 | [miniprogram/pages/super-article-editor/](miniprogram/pages/super-article-editor/):生成 `POST .../super/articles/generate`,发布 `POST .../super/articles`;图片走 `wx.uploadFile` 至 `/api/upload`,`formData.folder = article-images` |
-| 详情 | [miniprogram/pages/super-article-detail/](miniprogram/pages/super-article-detail/) |
-| 入口 | [miniprogram/pages/my/my.js](miniprogram/pages/my/my.js) 中 `superArticle`、`showSuperArticleEntry`;进编辑器前会调 `GET /api/miniprogram/my/super-stats` 校验 `isSuperIndividual` |
+| AI 写文 + 发布 | [miniprogram/pages/super-article-editor/](miniprogram/pages/super-article-editor/):多文件 `chooseMessageFile`;图片上传 `folder=article-images`,附件 `folder=book-attachments`,文本可读类型可本地 read 后并入 `referenceText`;生成 `POST .../super/articles/generate`(timeout 建议 ≥120s),发布 `POST .../super/articles`;统一 `utils/miniprogramUpload.js` → `POST /api/miniprogram/upload` |
+| **动态详情(正文展示)** | **`pages/read/read`**:`onLoad` 含 **`superArticleId`**(或 `saId`)时为超级个体详情模式(顶栏「文章详情」);正文/HTML、配图解析见 [miniprogram/utils/superArticleDetail.js](miniprogram/utils/superArticleDetail.js) |
+| 兼容旧链 | [miniprogram/pages/super-article-detail/](miniprogram/pages/super-article-detail/):仅 **`redirectTo`** → `read?superArticleId=` |
+| 我的动态列表 | [miniprogram/pages/super-article-mine/](miniprogram/pages/super-article-mine/) |
+| 入口 | [miniprogram/pages/my/my.js](miniprogram/pages/my/my.js) 中 **`superArticleMine`**、`showSuperArticleEntry`;进编辑器前会调 `GET /api/miniprogram/my/super-stats` 校验 `isSuperIndividual` |
- 所有请求**必须**走 `app.request` 与 **`/api/miniprogram/...` 前缀**(与 miniprogram-dev 一致)。
+- **数据仍独立**:`super_articles` 与书籍章节接口不合并;仅小程序 **UI** 共用 `read` 页面容器。
---
@@ -81,6 +84,6 @@ description: 超级个体 AI 写文章。super_articles 表、/api/miniprogram/s
## 7. 何时激活本 Skill
-- 改「超级个体」「AI 写文章」「super-article-editor」「super-article-detail」或 `super_articles`。
+- 改「超级个体」「AI 写文章」「super-article-editor」、**阅读页动态模式(`read` + `superArticleId`)**、`super-article-detail`(兼容重定向)或 `super_articles`。
- 需区分 **本书章节**、**MBTI 引流文** 与 **超级个体发文** 时。
- 设计草稿/审核/定时发等新能力前,先读本 Skill §2 与 §5,避免与现有表职责混淆。
diff --git a/miniprogram/pages/super-article-editor/super-article-editor.js b/miniprogram/pages/super-article-editor/super-article-editor.js
index 2c918379..2dfb28c2 100644
--- a/miniprogram/pages/super-article-editor/super-article-editor.js
+++ b/miniprogram/pages/super-article-editor/super-article-editor.js
@@ -15,8 +15,8 @@ function newAttachId() {
Page({
data: {
- /** 临时隐藏「AI 写文章」卡片;需上线时再设为 true */
- showAiWriteSection: false,
+ /** 「AI 帮你写」卡片:参考图 + 提示词 + 生成 */
+ showAiWriteSection: true,
statusBarHeight: 44,
auditMode: false,
title: '',
diff --git a/miniprogram/pages/super-article-editor/super-article-editor.wxml b/miniprogram/pages/super-article-editor/super-article-editor.wxml
index 2ac65f5d..ad20622e 100644
--- a/miniprogram/pages/super-article-editor/super-article-editor.wxml
+++ b/miniprogram/pages/super-article-editor/super-article-editor.wxml
@@ -11,7 +11,7 @@
AI 帮你写
- 参考图片(可多选)
+ 参考图片(可选)
+ 添加图片
@@ -25,7 +25,7 @@
移除
- 从相册或相机添加参考图,可多选(单次最多 9 张),上传后参与 AI 生成。单张建议不超 30MB。
+ 从相册或相机添加参考图,可选(单次最多 9 张),上传后与提示词一并交给 AI 分析。单张建议不超 30MB。
提示词
diff --git a/miniprogram/pages/super-moments/super-moments.js b/miniprogram/pages/super-moments/super-moments.js
index dcb02fa7..18480cc8 100644
--- a/miniprogram/pages/super-moments/super-moments.js
+++ b/miniprogram/pages/super-moments/super-moments.js
@@ -1,5 +1,5 @@
/**
- * 动态广场:超级个体图文列表(触底分页)
+ * 动态广场:超级个体图文列表(下拉刷新 + 触底分页加载)
*/
const app = getApp()
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
@@ -85,6 +85,9 @@ function mapFeedRows(rows) {
}
Page({
+ /** 递增序列:下拉刷新时废弃进行中的上拉请求结果,避免列表错乱 */
+ loadSeq: 0,
+
data: {
statusBarHeight: 44,
feedList: [],
@@ -126,38 +129,63 @@ Page({
this.loadMore()
},
+ async onPullDownRefresh() {
+ try {
+ await app.getAuditMode()
+ } catch (_) {}
+ if (app.globalData.auditMode) {
+ wx.stopPullDownRefresh()
+ return
+ }
+ try {
+ await this.reloadFeed()
+ } finally {
+ wx.stopPullDownRefresh()
+ }
+ },
+
async reloadFeed() {
+ this.loadSeq += 1
+ const seq = this.loadSeq
this.setData({
loading: true,
+ loadingMore: false,
page: 1,
feedList: [],
hasMore: true,
total: 0,
})
try {
- await this.fetchPage(1, true)
+ await this.fetchPage(1, true, seq)
} finally {
- this.setData({ loading: false })
+ if (seq === this.loadSeq) {
+ this.setData({ loading: false })
+ }
}
},
async loadMore() {
if (this.data.loading || this.data.loadingMore || !this.data.hasMore) return
+ const seq = this.loadSeq
const next = this.data.page + 1
this.setData({ loadingMore: true })
try {
- await this.fetchPage(next, false)
+ await this.fetchPage(next, false, seq)
} finally {
this.setData({ loadingMore: false })
}
},
- async fetchPage(page, replace) {
+ async fetchPage(page, replace, seq) {
const res = await app.request({
url: `/api/miniprogram/super/articles/feed?page=${encodeURIComponent(String(page))}&pageSize=${PAGE_SIZE}`,
silent: true,
})
+ if (seq !== this.loadSeq) {
+ return
+ }
if (!res?.success) {
+ if (seq !== this.loadSeq) return
wx.showToast({ title: String(res?.error || '加载失败'), icon: 'none' })
if (replace) this.setData({ feedList: [], hasMore: false, total: 0 })
return
diff --git a/miniprogram/pages/super-moments/super-moments.json b/miniprogram/pages/super-moments/super-moments.json
index 89389a5d..980d848a 100644
--- a/miniprogram/pages/super-moments/super-moments.json
+++ b/miniprogram/pages/super-moments/super-moments.json
@@ -2,7 +2,7 @@
"usingComponents": {
"icon": "/components/icon/icon"
},
- "enablePullDownRefresh": false,
+ "enablePullDownRefresh": true,
"onReachBottomDistance": 160,
"backgroundTextStyle": "light",
"backgroundColor": "#0b1220"
diff --git a/soul-api/.env.super-article-ai.example b/soul-api/.env.super-article-ai.example
new file mode 100644
index 00000000..c72b570c
--- /dev/null
+++ b/soul-api/.env.super-article-ai.example
@@ -0,0 +1,12 @@
+# 超级个体「发动态」→ AI 生成(OpenAI 兼容协议)
+# 智增增文档:https://doc.zhizengzeng.com/doc-3979947
+# 接入端点基地址(勿省略 /v1)
+SUPER_ARTICLE_AI_BASE_URL=https://api.zhizengzeng.com/v1
+# 控制台创建的 API Key(勿提交到 Git;复制到本机 .env / .env.development)
+SUPER_ARTICLE_AI_API_KEY=
+# 模型名以智增增控制台/文档为准;gpt-4o-mini 支持文本与参考图(vision)
+SUPER_ARTICLE_AI_MODEL=gpt-4o-mini
+# 用户上传参考图时:自动改用视觉模型多模态分析图片(与 SUPER_ARTICLE_AI_MODEL 可为不同模型)
+SUPER_ARTICLE_AI_VISION_MODEL=gpt-4o-mini
+
+# 若未配置以上项,则回退使用 OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL;视觉模型缺省为 gpt-4o-mini
diff --git a/soul-api/internal/config/config.go b/soul-api/internal/config/config.go
index 0cd96faf..770e3b7b 100644
--- a/soul-api/internal/config/config.go
+++ b/soul-api/internal/config/config.go
@@ -72,6 +72,12 @@ type Config struct {
OpenAIAPIKey string // OPENAI_API_KEY
OpenAIBaseURL string // OPENAI_BASE_URL,默认 https://api.openai.com/v1
OpenAIModel string // OPENAI_MODEL,默认 gpt-4o-mini
+
+ // 超级个体「发动态」AI 生成(默认同上;可单独指向智增增等网关,避免与其它 OPENAI 用途混 Key)
+ SuperArticleAIAPIKey string // SUPER_ARTICLE_AI_API_KEY,空则沿用 OPENAI_API_KEY
+ SuperArticleAIBaseURL string // SUPER_ARTICLE_AI_BASE_URL,空则沿用 OPENAI_BASE_URL
+ SuperArticleAIModel string // SUPER_ARTICLE_AI_MODEL,空则沿用 OPENAI_MODEL(纯文本生成)
+ SuperArticleAIVisionModel string // SUPER_ARTICLE_AI_VISION_MODEL,有参考图时用;默认 gpt-4o-mini
}
// BaseURLJoin 将路径拼接到 BaseURL,path 应以 / 开头
@@ -326,6 +332,23 @@ func Load() (*Config, error) {
aiModel = "gpt-4o-mini"
}
+ superArticleAIKey := strings.TrimSpace(os.Getenv("SUPER_ARTICLE_AI_API_KEY"))
+ if superArticleAIKey == "" {
+ superArticleAIKey = aiAPIKey
+ }
+ superArticleAIBase := strings.TrimSpace(os.Getenv("SUPER_ARTICLE_AI_BASE_URL"))
+ if superArticleAIBase == "" {
+ superArticleAIBase = aiBaseURL
+ }
+ superArticleAIModel := strings.TrimSpace(os.Getenv("SUPER_ARTICLE_AI_MODEL"))
+ if superArticleAIModel == "" {
+ superArticleAIModel = aiModel
+ }
+ superArticleAIVisionModel := strings.TrimSpace(os.Getenv("SUPER_ARTICLE_AI_VISION_MODEL"))
+ if superArticleAIVisionModel == "" {
+ superArticleAIVisionModel = "gpt-4o-mini"
+ }
+
cfg := &Config{
Port: port,
Mode: mode,
@@ -362,6 +385,10 @@ func Load() (*Config, error) {
OpenAIAPIKey: aiAPIKey,
OpenAIBaseURL: aiBaseURL,
OpenAIModel: aiModel,
+ SuperArticleAIAPIKey: superArticleAIKey,
+ SuperArticleAIBaseURL: superArticleAIBase,
+ SuperArticleAIModel: superArticleAIModel,
+ SuperArticleAIVisionModel: superArticleAIVisionModel,
}
// 生产环境(GIN_MODE=release)强制校验敏感配置,禁止使用默认值
diff --git a/soul-api/internal/handler/admin_super_articles.go b/soul-api/internal/handler/admin_super_articles.go
index f934aa11..b26f2f33 100644
--- a/soul-api/internal/handler/admin_super_articles.go
+++ b/soul-api/internal/handler/admin_super_articles.go
@@ -58,6 +58,7 @@ func AdminSuperArticlesList(c *gin.Context) {
list := make([]gin.H, 0, len(rows))
for _, r := range rows {
a := authorMap[strings.TrimSpace(r.UserID)]
+ an, aa := resolveSuperArticleAuthorDisplay(a)
list = append(list, gin.H{
"id": r.ID,
"userId": r.UserID,
@@ -66,8 +67,8 @@ func AdminSuperArticlesList(c *gin.Context) {
"images": parseSuperArticleImagesJSON(r.Images),
"auditStatus": strings.TrimSpace(r.AuditStatus),
"rejectReason": strings.TrimSpace(r.RejectReason),
- "authorNickname": strings.TrimSpace(a.Nickname),
- "authorAvatar": strings.TrimSpace(a.Avatar),
+ "authorNickname": an,
+ "authorAvatar": aa,
"createdAt": r.CreatedAt,
"updatedAt": r.UpdatedAt,
})
diff --git a/soul-api/internal/handler/h5_super_article.go b/soul-api/internal/handler/h5_super_article.go
index 20877e86..9f55babb 100644
--- a/soul-api/internal/handler/h5_super_article.go
+++ b/soul-api/internal/handler/h5_super_article.go
@@ -98,8 +98,9 @@ func H5SuperArticlePage(c *gin.Context) {
}
authorMap := loadSuperArticleAuthorMap([]string{row.UserID})
- author := authorMap[row.UserID]
- nick := strings.TrimSpace(author.Nickname)
+ author := authorMap[strings.TrimSpace(row.UserID)]
+ an, aa := resolveSuperArticleAuthorDisplay(author)
+ nick := an
if nick == "" {
nick = "超级个体"
}
@@ -141,7 +142,7 @@ func H5SuperArticlePage(c *gin.Context) {
ogImage = h5AbsolutizeImage(strings.TrimSpace(articleImages[0]), cfg.BaseURL)
}
if ogImage == "" {
- ogImage = strings.TrimSpace(author.Avatar)
+ ogImage = aa
if ogImage != "" {
ogImage = h5AbsolutizeImage(ogImage, cfg.BaseURL)
}
diff --git a/soul-api/internal/handler/miniprogram_ai_article.go b/soul-api/internal/handler/miniprogram_ai_article.go
index e43dcf88..d9584c1e 100644
--- a/soul-api/internal/handler/miniprogram_ai_article.go
+++ b/soul-api/internal/handler/miniprogram_ai_article.go
@@ -22,15 +22,47 @@ import (
const aiArticleSystemPrompt = `你是「卡若创业派对」的内容创作助手,帮助超级个体写出真实、有深度的商业与创业内容文章。
-写作要求:
-- 标题:精炼有力,≤30字,有吸引力,点明核心价值
-- 正文:600-1000字,有真实故事或案例,有观点洞察,语气自然真诚
-- 风格:像朋友分享经验,而非说教;有画面感,接地气
-- 不要使用"首先、其次、最后"等模板化结构词
+若用户提供的素材末尾附有「写文章规则提示词」或以「---」分隔的写作规范段落,你必须优先、严格按该段落成稿(体裁、字数、人称、版式、Markdown 交付与自检等)。
-严格输出 JSON,不要有任何额外文字:
+输出封装(始终遵守):只输出一段合法 JSON,不要有任何 JSON 外的文字。title 为主标题纯文本(可与正文首行 Markdown 标题语义一致,但不要带「#」前缀);content 为正文全文,可为 Markdown(含 **加粗**、分段、---、HTML 注释等),字符串内需合法 JSON 转义。
+
+若用户未附带上述写作规范段落,则默认:标题精炼有力≤30字;正文约600~1000字;语气自然真诚,像朋友分享经验;不要使用"首先、其次、最后"等模板化结构词。
+
+严格输出 JSON:
{"title":"文章标题","content":"文章正文"}`
+// superArticleWritingRulesSuffix 在服务端拼接到用户素材末尾(与 开发文档/ai开发文档.txt 写作规则段落保持一致)。
+const superArticleWritingRulesSuffix = `写文章规则提示词:
+
+写文章。严格按以下规则成稿。
+
+【材料】用户提供:本场聊天记录/纪要/妙记要点、场次号、主题短句;写作前先按规范阅读飞书运营报表,把报表上有的时长、场观、进房等具体数字写进开篇(只写一次)。
+
+【人称与禁用】叙述全程用「我」,禁止「房主」。禁止「这边」「那边」。禁止「回答说,……」——问句后直接接我的陈述。语气偏强势:干脆、笃定,少「可能/也许」。「卡若」每篇最多 1 次,可完全不出现。
+
+【刺点三对齐】正文第一行标题「# 第X场|短句」、磁盘文件名、开篇刺激性金句三者同一刺点;不写空泛抒情。
+
+【结构与比例】
+- 默认单场 采用「2026 每日派对干货 / 派对体」:三块约 40% / 20%~30% / 余下约 30%;块与块之间必须有过渡口语;每一块结束必须有一段「段末小结」单独成段,30~50 个汉字一句,禁止出现「干货」等标签字。
+
+【开篇】先有冲突或世俗金句,再切入场景。固定数据句式(报表有则写具体数,没有的项不写):「第 X 场,X 分钟;场观 XXXXX,进房 XXX 人。」不写「约」、不写「见报表」。时长/场观/进房仅在开篇出现一次。
+
+【中段】推进用时间线或事件线;多写对话(「X 号问」「有人问」);我的话直接陈述。约在全文 50% 处插入一段浓缩要点 3~6 条(20~40 字/条),前后用 --- 分隔,条目中禁止「干货」二字。
+
+【分享句】约在 20% 处一句、结尾一句,各 ≤50 字,紧扣本节,禁止「干货:」字样。
+
+【强调·派对体成稿必选】Markdown **加粗**:至少 2 句完整立场/结论句(分布在不同段落);至少 3 个不同的重点关键词加粗。禁止堆砌无意义加粗。
+
+【结尾】升华主题;派对体可有文末小结(3~6 条短金句)+ 总结段落 + 一句收尾金句。
+
+【字数与真实性】小节 3000~5000 字;忠于来源不改原意;所有数字必须有依据、写具体算法/金额/人次等,Soul 侧常用口径可参考规范中的曝光/进房/成本写法。
+
+【隐晦植入】文中自然散布 2~4 处:联系管理、做切片、做副业相关一句,不单段喊话,随机10%加率不用每篇文章都加
+
+【版式强制】一句一行,句间空一行;分号也拆成行;段落之间空行;大段堆砌禁止。
+
+【交付】输出 Markdown;标题一行「# 第X场|…」;成稿末尾按模板追加 HTML 注释自检:「我」字数/次数、本场关键字频次、关键字加粗处数、重点句/词加粗是否达标。`
+
const (
maxArticleMaterialFetchBody = 512 * 1024 // 单素材最大读取
maxCombinedReferenceRunes = 60000 // 合并后截断上限(含提示与素材摘要)
@@ -40,11 +72,11 @@ const (
// 根据提示词 + 可选参考文本 / 素材文件 URL(本站上传链接) / 图片 URL,调用 AI 生成草稿(仅超级个体)。
func MiniprogramSuperArticleGenerate(c *gin.Context) {
var req struct {
- UserID string `json:"userId"`
- Description string `json:"description"` // 提示词(与原字段兼容)
- ReferenceText string `json:"referenceText"` // 小程序端可读出的纯文本(多文件拼接)
- MaterialURLs []string `json:"materialUrls"` // 上传后的文件 URL(服务端在安全域名内抓取文本)
- ImageURLs []string `json:"imageUrls"`
+ UserID string `json:"userId"`
+ Description string `json:"description"` // 提示词(与原字段兼容)
+ ReferenceText string `json:"referenceText"` // 小程序端可读出的纯文本(多文件拼接)
+ MaterialURLs []string `json:"materialUrls"` // 上传后的文件 URL(服务端在安全域名内抓取文本)
+ ImageURLs []string `json:"imageUrls"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
@@ -86,8 +118,8 @@ func MiniprogramSuperArticleGenerate(c *gin.Context) {
}
cfg := config.Get()
- if cfg == nil || strings.TrimSpace(cfg.OpenAIAPIKey) == "" {
- c.JSON(http.StatusOK, gin.H{"success": false, "error": "AI 功能暂未开放,请联系管理员配置"})
+ if cfg == nil || strings.TrimSpace(cfg.SuperArticleAIAPIKey) == "" {
+ c.JSON(http.StatusOK, gin.H{"success": false, "error": "AI 功能暂未开放,请在服务端配置 SUPER_ARTICLE_AI_API_KEY 或 OPENAI_API_KEY"})
return
}
@@ -102,6 +134,7 @@ func MiniprogramSuperArticleGenerate(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": errMsg})
return
}
+ combined = appendSuperArticleWritingRules(combined)
title, content, err := aiGenerateArticle(cfg, combined, validImageURLs)
if err != nil {
@@ -156,6 +189,32 @@ func assembleSuperArticlePrompt(cfg *config.Config, prompt, refText string, mate
return combined, ""
}
+// appendSuperArticleWritingRules 在用户提示词与参考素材组装结果末尾追加固定写作规则(保证规则段不被 maxCombinedReferenceRunes 截断)。
+func appendSuperArticleWritingRules(combined string) string {
+ suffix := strings.TrimSpace(superArticleWritingRulesSuffix)
+ if suffix == "" {
+ return strings.TrimSpace(combined)
+ }
+ suffixRunes := len([]rune(suffix))
+ sep := "\n\n---\n\n"
+ sepLen := len([]rune(sep))
+ maxBody := maxCombinedReferenceRunes - suffixRunes - sepLen
+ if maxBody < 0 {
+ maxBody = 0
+ }
+ base := strings.TrimSpace(combined)
+ if base != "" {
+ rs := []rune(base)
+ if len(rs) > maxBody {
+ base = string(rs[:maxBody]) + "\n\n(上文参考已截断)"
+ }
+ }
+ if base == "" {
+ return suffix
+ }
+ return base + sep + suffix
+}
+
func snippetNameFromURL(raw string) string {
u, err := url.Parse(strings.TrimSpace(raw))
if err != nil {
@@ -281,34 +340,66 @@ func looksLikeUTF8ArticleRef(b []byte) bool {
return float64(printable)/float64(total) > 0.90
}
+// superArticleModelLikelySupportsVision 粗略判断 chat/completions 模型是否适合传 image_url(智增增/OpenAI 等多网关)。
+func superArticleModelLikelySupportsVision(model string) bool {
+ m := strings.ToLower(strings.TrimSpace(model))
+ if m == "" {
+ return false
+ }
+ if strings.Contains(m, "vision") || strings.Contains(m, "-vl") || strings.Contains(m, "vqa") {
+ return true
+ }
+ if strings.Contains(m, "gpt-4") || strings.Contains(m, "gpt-5") || strings.Contains(m, "gpt-4o") {
+ return true
+ }
+ if strings.Contains(m, "claude-3") || strings.Contains(m, "claude-4") {
+ return true
+ }
+ if strings.Contains(m, "glm-4v") || strings.Contains(m, "qwen-vl") || strings.Contains(m, "gemini") {
+ return true
+ }
+ return false
+}
+
// aiGenerateArticle 调用 OpenAI 兼容 API 生成文章标题和正文。
-// 若有图片 URL 且模型支持视觉,以多模态格式传入;否则退化为纯文本。
+// 若有参考图片 URL:使用多模态 user content(text + image_url);当前主模型不支持 vision 时自动改用 SUPER_ARTICLE_AI_VISION_MODEL(默认同为 gpt-4o-mini)。
func aiGenerateArticle(cfg *config.Config, description string, imageURLs []string) (title, content string, err error) {
- apiKey := strings.TrimSpace(cfg.OpenAIAPIKey)
- baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.OpenAIBaseURL), "/")
+ apiKey := strings.TrimSpace(cfg.SuperArticleAIAPIKey)
+ baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.SuperArticleAIBaseURL), "/")
if baseURL == "" {
baseURL = "https://api.openai.com/v1"
}
- model := strings.TrimSpace(cfg.OpenAIModel)
- if model == "" {
- model = "gpt-4o-mini"
+ baseModel := strings.TrimSpace(cfg.SuperArticleAIModel)
+ if baseModel == "" {
+ baseModel = "gpt-4o-mini"
}
- // 判断模型是否支持视觉(OpenAI gpt-4 系列 / claude-3 系列)
- supportsVision := len(imageURLs) > 0 && (
- strings.Contains(model, "gpt-4") ||
- strings.Contains(model, "claude-3") ||
- strings.Contains(model, "vision"))
+ requestModel := baseModel
+ useVision := len(imageURLs) > 0
+ if useVision {
+ if superArticleModelLikelySupportsVision(baseModel) {
+ requestModel = baseModel
+ } else {
+ vm := strings.TrimSpace(cfg.SuperArticleAIVisionModel)
+ if vm == "" {
+ vm = "gpt-4o-mini"
+ }
+ requestModel = vm
+ }
+ }
var userContent interface{}
- if supportsVision {
+ if useVision {
parts := []map[string]interface{}{
{"type": "text", "text": buildUserPrompt(description, imageURLs, true)},
}
for _, u := range imageURLs {
parts = append(parts, map[string]interface{}{
- "type": "image_url",
- "image_url": map[string]string{"url": u, "detail": "low"},
+ "type": "image_url",
+ "image_url": map[string]string{
+ "url": u,
+ "detail": "low",
+ },
})
}
userContent = parts
@@ -317,16 +408,16 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
}
reqBody, _ := json.Marshal(map[string]interface{}{
- "model": model,
+ "model": requestModel,
"messages": []map[string]interface{}{
{"role": "system", "content": aiArticleSystemPrompt},
{"role": "user", "content": userContent},
},
"temperature": 0.8,
- "max_tokens": 2000,
+ "max_tokens": 8192,
})
- ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
+ ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
httpReq, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/chat/completions", bytes.NewReader(reqBody))
@@ -341,7 +432,10 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
return "", "", fmt.Errorf("请求 AI 服务失败: %v", err)
}
defer resp.Body.Close()
- body, _ := io.ReadAll(resp.Body)
+ body, errRead := io.ReadAll(resp.Body)
+ if errRead != nil {
+ return "", "", fmt.Errorf("读取 AI 响应失败: %v", errRead)
+ }
var apiResp struct {
Choices []struct {
@@ -354,10 +448,23 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
} `json:"error"`
}
if err := json.Unmarshal(body, &apiResp); err != nil {
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ return "", "", fmt.Errorf("AI 服务 HTTP %d", resp.StatusCode)
+ }
return "", "", fmt.Errorf("解析 AI 响应失败")
}
- if apiResp.Error != nil {
- return "", "", fmt.Errorf("%s", apiResp.Error.Message)
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+ if apiResp.Error != nil && strings.TrimSpace(apiResp.Error.Message) != "" {
+ return "", "", fmt.Errorf("%s", strings.TrimSpace(apiResp.Error.Message))
+ }
+ snippet := strings.TrimSpace(string(body))
+ if len(snippet) > 400 {
+ snippet = snippet[:400] + "…"
+ }
+ return "", "", fmt.Errorf("AI 服务 HTTP %d %s", resp.StatusCode, snippet)
+ }
+ if apiResp.Error != nil && strings.TrimSpace(apiResp.Error.Message) != "" {
+ return "", "", fmt.Errorf("%s", strings.TrimSpace(apiResp.Error.Message))
}
if len(apiResp.Choices) == 0 || apiResp.Choices[0].Message.Content == "" {
return "", "", fmt.Errorf("AI 无返回内容")
@@ -410,12 +517,12 @@ func parseArticleJSON(raw string) (title, content string, err error) {
return "", "", fmt.Errorf("AI 生成内容不完整,请重试")
}
- // 截断超限内容
- if tr := []rune(title); len(tr) > 40 {
- title = string(tr[:40])
+ // 截断超限内容(派对体长文可到数千字,预留上限避免极端响应撑爆存储)
+ if tr := []rune(title); len(tr) > 120 {
+ title = string(tr[:120])
}
- if cr := []rune(content); len(cr) > 5000 {
- content = string(cr[:5000])
+ if cr := []rune(content); len(cr) > 20000 {
+ content = string(cr[:20000])
}
return title, content, nil
diff --git a/soul-api/internal/handler/miniprogram_super_articles.go b/soul-api/internal/handler/miniprogram_super_articles.go
index a660c5b3..7a2d55f1 100644
--- a/soul-api/internal/handler/miniprogram_super_articles.go
+++ b/soul-api/internal/handler/miniprogram_super_articles.go
@@ -14,9 +14,24 @@ import (
)
type superArticleAuthorRow struct {
- ID string `gorm:"column:id"`
- Nickname string `gorm:"column:nickname"`
- Avatar string `gorm:"column:avatar"`
+ ID string `gorm:"column:id"`
+ Nickname string `gorm:"column:nickname"`
+ Avatar string `gorm:"column:avatar"`
+ VipName string `gorm:"column:vip_name"`
+ VipAvatar string `gorm:"column:vip_avatar"`
+}
+
+// resolveSuperArticleAuthorDisplay 展示用昵称/头像:普通资料优先,超级个体常用 vip_* 兜底。
+func resolveSuperArticleAuthorDisplay(a superArticleAuthorRow) (nickname, avatar string) {
+ nickname = strings.TrimSpace(a.Nickname)
+ if nickname == "" {
+ nickname = strings.TrimSpace(a.VipName)
+ }
+ avatar = strings.TrimSpace(a.Avatar)
+ if avatar == "" {
+ avatar = strings.TrimSpace(a.VipAvatar)
+ }
+ return nickname, avatar
}
func loadSuperArticleAuthorMap(ids []string) map[string]superArticleAuthorRow {
@@ -26,7 +41,7 @@ func loadSuperArticleAuthorMap(ids []string) map[string]superArticleAuthorRow {
}
var rows []superArticleAuthorRow
db := database.DB()
- _ = db.Table("users").Select("id", "nickname", "avatar").Where("id IN ?", ids).Find(&rows).Error
+ _ = db.Table("users").Select("id", "nickname", "avatar", "vip_name", "vip_avatar").Where("id IN ?", ids).Find(&rows).Error
for _, r := range rows {
key := strings.TrimSpace(r.ID)
if key == "" {
@@ -142,6 +157,7 @@ func MiniprogramSuperArticleFeed(c *gin.Context) {
preview = string([]rune(preview)[:120]) + "..."
}
a := authorMap[strings.TrimSpace(r.UserID)]
+ an, aa := resolveSuperArticleAuthorDisplay(a)
list = append(list, gin.H{
"id": r.ID,
"userId": r.UserID,
@@ -149,8 +165,8 @@ func MiniprogramSuperArticleFeed(c *gin.Context) {
"content": r.Content,
"preview": preview,
"images": parseSuperArticleImagesJSON(r.Images),
- "authorNickname": strings.TrimSpace(a.Nickname),
- "authorAvatar": strings.TrimSpace(a.Avatar),
+ "authorNickname": an,
+ "authorAvatar": aa,
"createdAt": r.CreatedAt,
})
}
@@ -343,6 +359,7 @@ func MiniprogramSuperArticleList(c *gin.Context) {
authorMap := loadSuperArticleAuthorMap([]string{authorUserID})
author := authorMap[authorUserID]
+ an, aa := resolveSuperArticleAuthorDisplay(author)
list := make([]gin.H, 0, len(rows))
for _, r := range rows {
content := strings.TrimSpace(r.Content)
@@ -357,8 +374,8 @@ func MiniprogramSuperArticleList(c *gin.Context) {
"content": r.Content,
"preview": preview,
"images": parseSuperArticleImagesJSON(r.Images),
- "authorNickname": strings.TrimSpace(author.Nickname),
- "authorAvatar": strings.TrimSpace(author.Avatar),
+ "authorNickname": an,
+ "authorAvatar": aa,
"createdAt": r.CreatedAt,
})
}
@@ -409,6 +426,7 @@ func MiniprogramSuperArticleMine(c *gin.Context) {
authorMap := loadSuperArticleAuthorMap([]string{userID})
author := authorMap[userID]
+ an, aa := resolveSuperArticleAuthorDisplay(author)
list := make([]gin.H, 0, len(rows))
for _, r := range rows {
content := strings.TrimSpace(r.Content)
@@ -427,8 +445,8 @@ func MiniprogramSuperArticleMine(c *gin.Context) {
"content": r.Content,
"preview": preview,
"images": parseSuperArticleImagesJSON(r.Images),
- "authorNickname": strings.TrimSpace(author.Nickname),
- "authorAvatar": strings.TrimSpace(author.Avatar),
+ "authorNickname": an,
+ "authorAvatar": aa,
"createdAt": r.CreatedAt,
"auditStatus": ast,
"rejectReason": strings.TrimSpace(r.RejectReason),
@@ -474,7 +492,8 @@ func MiniprogramSuperArticleDetail(c *gin.Context) {
ast = model.SuperArticleAuditApproved
}
authorMap := loadSuperArticleAuthorMap([]string{row.UserID})
- author := authorMap[row.UserID]
+ author := authorMap[strings.TrimSpace(row.UserID)]
+ an, aa := resolveSuperArticleAuthorDisplay(author)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
@@ -483,8 +502,8 @@ func MiniprogramSuperArticleDetail(c *gin.Context) {
"title": row.Title,
"content": row.Content,
"images": parseSuperArticleImagesJSON(row.Images),
- "authorNickname": strings.TrimSpace(author.Nickname),
- "authorAvatar": strings.TrimSpace(author.Avatar),
+ "authorNickname": an,
+ "authorAvatar": aa,
"createdAt": row.CreatedAt,
"auditStatus": ast,
"rejectReason": strings.TrimSpace(row.RejectReason),
diff --git a/开发文档/10、项目管理/运营与变更.md b/开发文档/10、项目管理/运营与变更.md
index b7436e0e..57278203 100644
--- a/开发文档/10、项目管理/运营与变更.md
+++ b/开发文档/10、项目管理/运营与变更.md
@@ -6,6 +6,27 @@
## 一、近期同步(摘要)
+### 2026-05-09 超级个体「动态详情」并入小程序阅读页(文章详情)
+
+- **动机**:产品与章节阅读统一为同一套「文章详情」承载页;动态分享与 H5 拉起小程序路径对齐。
+- **小程序**:`pages/read/read?superArticleId={数字}`(兼容 `saId`);`data.pageMode` 为 `super_article` 时渲染动态 UI(顶栏文案「文章详情」)。解析逻辑见 `miniprogram/utils/superArticleDetail.js`。
+- **兼容**:`pages/super-article-detail` 保留注册,`onLoad` 内 `redirectTo` 至 `read?superArticleId=`。
+- **入口更新**:`super-article-mine`、`super-moments`、`member-detail` 等跳转已改为阅读页 query。
+- **后端**:`h5_super_article.go` 中拉起小程序 path 已改为 `pages/read/read?superArticleId=...`。
+- **文档**:`开发文档/4、前端/当前小程序开发细则.md` §2.2.2。
+
+### 2026-05-09 小程序「我的」页快捷入口 UI 优化
+
+- **范围**:`pages/my/my.wxml`、`pages/my/my.wxss`,以及 `miniprogram/assets/icons/list-teal.svg`、`share-teal.svg`、`wallet-teal.svg`、`users-teal.svg`、`book-arrow-teal.svg`。
+- **说明**:快捷入口图标改为渐变面性 SVG;每项增加圆形发光底托(`stat-icon-wrap`)、卡片渐变边框与弱外发光;余额主文案使用 `stat-num-balance`(渐变字,低版本可降级纯色)。**`book-arrow-teal.svg`** 与「最近阅读」卡片标题共用,改图标时需注意两处观感一致。
+- **交叉引用**:实现约定见 `开发文档/4、前端/当前小程序开发细则.md` §2.2.1。
+
+### 2026-05-09 超级个体「AI 写文章」:多文件 + 提示词 + 生成草稿
+
+- **小程序**:`pages/super-article-editor/super-article-editor`,`wx.chooseMessageFile` 多选;图片上传 `folder=article-images`,附件 `folder=book-attachments`,文本类可本地读入后随 `referenceText` 提交;`POST /api/miniprogram/super/articles/generate` 建议超时 120s;上传走 `utils/miniprogramUpload.js`(Bearer)。
+- **后端**:`/super/articles/generate` 支持 `description`、`referenceText`、`materialUrls`、`imageUrls`,至少其一;`materialUrls` 仅允许白名单域拉取(防 SSRF)。
+- **文档**:`开发文档/5、接口/API接口完整文档.md` §1.3、`开发文档/三端需求业务对齐-小程序与API.md` §四、`开发文档/4、前端/当前小程序开发细则.md` §2.2.3、`.cursor/skills/super-individual-article/SKILL.md`。
+
### 2026-04-28 超级个体发文章接口对接与排障
- **目标**:小程序超级个体发布文章,统一走 `super_articles` 表对应接口,避免与管理端文章体系混用。
@@ -34,4 +55,4 @@
---
-**最后更新**:2026-04-15
+**最后更新**:2026-05-09
diff --git a/开发文档/4、前端/当前小程序开发细则.md b/开发文档/4、前端/当前小程序开发细则.md
index 1aad3fe1..1218fbdb 100644
--- a/开发文档/4、前端/当前小程序开发细则.md
+++ b/开发文档/4、前端/当前小程序开发细则.md
@@ -1,7 +1,7 @@
# 当前小程序开发细则
> 汇总当前 Soul 创业派对小程序的架构、经验与规划,便于新人上手与后续迭代。
-> 最后整理:2026-02
+> 最后整理:2026-05-09(§2.2.2 阅读页承载超级个体动态;§2.2.3 AI 发文章)
---
@@ -52,6 +52,33 @@ miniprogram/
非 Tab 页:阅读页 read、推广中心 referral、订单 purchases、设置 settings、搜索 search、关于 about。
+### 2.2.1 我的页「快捷入口」(UI 约定,2026-05)
+
+- **位置**:`pages/my/my` 中「快捷入口」卡片:`stats-card` → `stats-grid` → 若干 `stat-box`。
+- **结构**:每个入口为 `stat-box`;图标外包一层 `stat-icon-wrap`(圆形渐变底、描边与弱外发光);列表项设 `hover-class="stat-box-hover"` 提供按压反馈。
+- **图标资源**(`miniprogram/assets/icons/`):`list-teal`(订单)、`share-teal`(代付)、`wallet-teal`(余额)、`users-teal`(客资,条件展示)、`book-arrow-teal`(发文,条件展示)。均为青绿渐变面性 SVG。
+- **共用资源**:`book-arrow-teal.svg` 同时用于同页「最近阅读」卡片标题左侧图标,修改该文件需兼顾两处视觉。
+- **余额文案**:主数字 class 为 `stat-num-balance`(渐变字 + 等宽数字倾向);若某端 `background-clip: text` 表现不佳,可降级为纯色 `#4FD1C5`。
+
+### 2.2.2 阅读页 `read` 双模式:书籍章节 + 超级个体动态(文章详情)
+
+- **同一页面**:`pages/read/read` 既承载**书籍章节阅读**(付费墙、上下篇、阅读进度等),也承载 **超级个体 UGC 动态的正文详情展示**(与原先独立「动态详情」页合并)。
+- **区分参数(必选其一语义)**
+ - **章节**:`id`(章节 id)、可选 `mid`(章节 mid)、及原有 `ref`/`gift`/scene 等;逻辑不变。
+ - **动态**:**`superArticleId`**(数字字符串,表 `super_articles.id`);兼容简写 **`saId`**。**不要**用章节用的 `id` 传动态 id,避免与章节 id 混淆。
+- **落地路径示例**:`/pages/read/read?superArticleId=123`;分享卡片 / 朋友圈已为该格式。
+- **兼容**:`pages/super-article-detail/super-article-detail?id=` 仍注册在 `app.json`,进入后 **`redirectTo`** 到上述 `read?superArticleId=`(旧分享链接可用)。
+- **相关页面**:`super-article-editor`(编辑/发布)、`super-article-mine`(我的动态列表)、`super-moments`(广场)、`member-detail`(嘉宾动态入口)等跳转详情时统一 **`navigateTo` 阅读页 + `superArticleId`**。
+- **工具**:动态详情用到的正文/HTML、配图解析见 `miniprogram/utils/superArticleDetail.js`(由 `read.js` 引用)。
+- **后端 H5**:拉起小程序路径已对齐为 `pages/read/read?superArticleId=...`(见 `soul-api/internal/handler/h5_super_article.go`)。
+
+### 2.2.3 超级个体「发文章」页:多文件参考 + AI 生成 + 发布(2026-05)
+
+- **页面**:`pages/super-article-editor/super-article-editor`。
+- **流程**:选择多份参考文件 → 填写提示词 → **生成文章**(`POST /api/miniprogram/super/articles/generate`,建议 `timeout` ≥ 120s)自动回填标题与正文 → 可微调后 **发布文章**(`POST /api/miniprogram/super/articles`)。
+- **选文件**:`wx.chooseMessageFile` 多选。**图片**走 `POST /api/miniprogram/upload` + `folder=article-images`;**PDF/Office 等附件**走 `folder=book-attachments`;**`.txt` / `.md` / `.json` / `.csv` 等**可在端上 `readFile` 后并入请求体 `referenceText`(详见 `开发文档/5、接口/API接口完整文档.md` §1.3)。
+- **上传封装**:`utils/miniprogramUpload.js`(Bearer 与 baseUrl 一致)。
+
### 2.3 全局状态(app.js globalData)
- `userInfo`:登录后用户信息(id、openId、nickname、purchasedSections、hasFullBook、referralCode、referralCount 等)。
@@ -95,6 +122,7 @@ miniprogram/
### 3.4 分享与落地
- 阅读页分享:`onShareAppMessage` / `onShareTimeline` 带 `id=章节ID&ref=当前用户邀请码`,落地后 ref 写入 storage,绑定与订单归属同上。
+- **超级个体动态**(阅读页 `pageMode=super_article`):分享路径为 `/pages/read/read?superArticleId=…`,与章节链路分离。
- 文章/章节分销与全局同一套:不按“哪篇文章带来”单独分成或统计,仅按“谁发的链接(ref=谁)”归属。
---
diff --git a/开发文档/5、接口/API接口完整文档.md b/开发文档/5、接口/API接口完整文档.md
index 2fa96790..1d036136 100644
--- a/开发文档/5、接口/API接口完整文档.md
+++ b/开发文档/5、接口/API接口完整文档.md
@@ -73,7 +73,59 @@ Authorization: Bearer admin-token-secret
**接口**: `GET /api/miniprogram/super/articles/{id}`
**说明**:
-- 返回文章详情(标题、正文、作者昵称头像、创建时间)
+- 返回文章详情(标题、正文、配图、作者昵称头像、创建时间、审核状态等,以后端实现为准)。
+- 审核中非公开稿件:作者查看须在请求中带 `viewerUserId`(当前用户 openId),与 soul-api `MiniprogramSuperArticleDetail` 一致。
+
+### 删除文章(作者本人)
+
+**接口**: `DELETE /api/miniprogram/super/articles/{id}?userId={openId}`
+
+**兼容**: `POST /api/miniprogram/super/articles/{id}/delete?userId=`(语义同 DELETE,便于调试)。
+
+### 小程序落地页(详情 UI)
+
+- **统一使用阅读页**:`pages/read/read?superArticleId={id}`(兼容 query **`saId`**)。**勿**使用章节参数 `id` 传递动态主键,以免与章节 id 混淆。
+- **旧版路径**:`pages/super-article-detail/super-article-detail?id=` 仍可用,进入后重定向至上述路径。
+- **H5 拉起小程序**:服务端生成的 path 为 `pages/read/read?superArticleId=...`(见 `h5_super_article.go`)。
+
+### AI 生成文章草稿(多文件 + 提示词)
+
+**接口**: `POST /api/miniprogram/super/articles/generate`
+
+**请求体**:
+```json
+{
+ "userId": "小程序当前用户 id,必填",
+ "description": "提示词,可选;可与参考素材组合",
+ "referenceText": "可选,端上读取的纯文本(如多份 .txt/.md 拼接)",
+ "materialUrls": ["可选,附件上传接口返回的完整 URL"],
+ "imageUrls": ["可选,图片上传接口返回的公网 URL"]
+}
+```
+
+**校验**:
+- 仅**超级个体**可调(与发布接口同一身份判定)。
+- `description`、`referenceText` 非空、`materialUrls`、`imageUrls` **四者至少一项有内容**;若仅有图片且无文字素材,服务端会补充默认写作说明再调模型。
+
+**响应**:
+```json
+{ "success": true, "data": { "title": "...", "content": "..." } }
+```
+
+**运行依赖**: 服务端需配置 OpenAI 兼容通道(如 `OPENAI_API_KEY`,及可选 `OPENAI_BASE_URL`、`OPENAI_MODEL`)。
+
+**小程序对接要点**(页面:`pages/super-article-editor/super-article-editor`):
+- 会话内选文件:`wx.chooseMessageFile`(可多选)。
+- **图片**:`POST /api/miniprogram/upload`,`formData.folder = article-images`,将返回 URL 填入 `imageUrls`。
+- **附件**(PDF、Office、zip 等):`folder = book-attachments`(走附件校验与大小上限,与章节附件同源策略),URL 填入 `materialUrls`。
+- **可读文本**(`.txt`、`.md`、`.json`、`.csv` 等):可在端上用 `FileSystemManager.readFile` 读入 UTF-8,拼进 `referenceText`(过长时端上可先截断,如约 2.8 万字符再提交)。
+- 上传建议使用 `utils/miniprogramUpload.js`,请求头携带 `Authorization: Bearer `。
+- 生成接口耗时长,建议 `wx.request` / `app.request` **timeout ≥ 120000**(毫秒)。
+
+**服务端素材拉取(materialUrls)**:
+- 仅允许拉取 URL 路径含 **`/uploads/`**,且主机为 **配置项 `API_BASE_URL` 对应主机**或 **阿里云 OSS 域名(含 `*.aliyuncs.com`)**,用于降低 SSRF 风险。
+- 单文件最多读取约 **512KB**;合并后的参考正文在服务端约 **6 万字符**处截断。
+- PDF/Office 等二进制文件通常无法在服务端抽出正文,接口会在提示中说明「未解析」;产品侧可引导用户改用 `.txt/.md` 或把要点写在 `description`。
---
diff --git a/开发文档/5、接口/README.md b/开发文档/5、接口/README.md
index c7e72faf..bb54651d 100644
--- a/开发文档/5、接口/README.md
+++ b/开发文档/5、接口/README.md
@@ -7,6 +7,7 @@
| 文档 | 说明 |
|------|------|
| [**API接口完整文档.md**](./API接口完整文档.md) | Soul `/api` **主真源**(REST 模块划分、鉴权说明) |
+| [**文件上传与OSS.md**](./文件上传与OSS.md) | **路由摘要**;OSS/本地策略与运维见 [6、后端/阿里云OSS与文件上传.md](../6、后端/阿里云OSS与文件上传.md) |
| [配置清单-完整版.md](./配置清单-完整版.md) | 环境变量与配置项 |
| [在线支付对接文档.md](./在线支付对接文档.md) | 支付对接 |
| [接口与提现.md](./接口与提现.md) | 提现与相关接口 |
diff --git a/开发文档/5、接口/文件上传与OSS.md b/开发文档/5、接口/文件上传与OSS.md
new file mode 100644
index 00000000..e42a00db
--- /dev/null
+++ b/开发文档/5、接口/文件上传与OSS.md
@@ -0,0 +1,23 @@
+# 文件上传与 OSS(接口侧摘要)
+
+> **完整约定与运维排障**:见 [**6、后端/阿里云OSS与文件上传.md**](../6、后端/阿里云OSS与文件上传.md)(以 `internal/oss`、`upload.go` 为准)。本节仅列路由与语义,避免与后端长文重复。最后更新:2026-05-09。
+
+## 路由摘要
+
+| 方法与路径 | 鉴权 | 说明 |
+|-----------|------|------|
+| `POST /api/upload` | 无硬性要求 | multipart:`file`、`folder` |
+| `POST /api/admin/upload` | **Admin JWT** | **soul-admin**:`ADMIN_UPLOAD_PATH` |
+| `DELETE /api/upload`、`DELETE /api/admin/upload` | 后者须 Admin | query:`path=` |
+| `POST /api/miniprogram/upload` | - | 与 `UploadPost` 同源 |
+| `POST /api/miniprogram/upload/image`、`/video` | - | `upload_content`,图可压缩 |
+
+## 行为摘要
+
+**先 OSS(`oss_config` 有效),失败或未配置则落本地**;接口多返回 `success: true`。成功时 **`data.storage`**:`oss` | `local`(`/api/upload` 系列)。
+
+## 脚本
+
+- 缺少 `super_articles` 表:`soul-api/scripts/create_super_articles.sql`
+
+返回 [本目录 README](./README.md) · [后端完整说明](../6、后端/阿里云OSS与文件上传.md) · [开发文档索引](../索引.md)
diff --git a/开发文档/5、接口/配置清单-完整版.md b/开发文档/5、接口/配置清单-完整版.md
index 1ca859a7..688eb2d9 100644
--- a/开发文档/5、接口/配置清单-完整版.md
+++ b/开发文档/5、接口/配置清单-完整版.md
@@ -1,6 +1,6 @@
# Soul创业实验 - API密钥与配置清单
-> 最后更新: 2026-01-25
+> 最后更新: 2026-05-09(补充 OSS 业务文档链接)
> 维护人: 卡若
> ⚠️ 本文件包含敏感信息,请勿公开
@@ -87,6 +87,8 @@
| **AccessKey ID** | `LTAI5t9zkiWmFtHG8qmtdysW` |
| **AccessKey Secret** | `xxjXnZGLNvA2zDkj0aEBSQm3XZAaro` |
+**Soul 业务 OSS(素材/头像等)**:以库表 `system_config.oss_config` 为主;可选环境变量 `ALIYUN_OSS_ACCESS_KEY_ID` / `ALIYUN_OSS_ACCESS_KEY_SECRET` 在库内 Secret 无效时兜底。详见 [**开发文档/6、后端/阿里云OSS与文件上传.md**](../6、后端/阿里云OSS与文件上传.md)。
+
---
## 五、数据库
diff --git a/开发文档/6、后端/README.md b/开发文档/6、后端/README.md
index a361e469..7f6853db 100644
--- a/开发文档/6、后端/README.md
+++ b/开发文档/6、后端/README.md
@@ -7,6 +7,7 @@
| 文件 | 说明 |
|------|------|
| [后端架构.md](./后端架构.md) | 服务架构与模块 |
+| [**阿里云OSS与文件上传.md**](./阿里云OSS与文件上传.md) | **OSS 配置、环境变量兜底、上传策略、路由与排障**(路由短表互链 [5、接口/文件上传与OSS.md](../5、接口/文件上传与OSS.md)) |
| [后端开发规范.md](./后端开发规范.md) | 编码、路由、响应约定 |
| [内容创建问题修复说明.md](./内容创建问题修复说明.md) | 内容创建专项 |
| [soul-admin与Mycontent-temp内容页对比.md](./soul-admin与Mycontent-temp内容页对比.md) | 管理端内容页对比 |
diff --git a/开发文档/6、后端/阿里云OSS与文件上传.md b/开发文档/6、后端/阿里云OSS与文件上传.md
new file mode 100644
index 00000000..a7dc99ac
--- /dev/null
+++ b/开发文档/6、后端/阿里云OSS与文件上传.md
@@ -0,0 +1,86 @@
+# 阿里云 OSS 与文件上传(soul-api)
+
+> **以代码为准**:`soul-api/internal/oss`、`internal/handler/upload.go`、`internal/handler/upload_content.go`、`internal/handler/db.go`(系统设置)。
+> 最后更新:2026-05-09
+
+---
+
+## 1. 配置存哪里
+
+| 来源 | 说明 |
+|------|------|
+| **主配置** | MySQL 表 `system_config`,`config_key = 'oss_config'`,`config_value` 为 JSON |
+| **环境变量兜底** | 当库里的 `accessKeySecret` 为空或为占位符(如 `****`、全星号)时,可用 `ALIYUN_OSS_ACCESS_KEY_ID` / `ALIYUN_OSS_ACCESS_KEY_SECRET` 补齐后再校验(见 `soul-api/.env.development` 注释) |
+
+JSON 字段与管理端「系统设置 → OSS」一致,常见键名:
+
+- `endpoint`:如 `oss-cn-beijing.aliyuncs.com`(可不含 `https://`)
+- `bucket`、`region`
+- `accessKeyId`、`accessKeySecret`
+- `publicBaseUrl`(可选):CDN 或自定义域名,`https://` 开头或裸域名皆可
+
+**说明**:RAM 控制台下发的 **AccessKey Secret 为长随机串**。若库里误存字面量 `****`,阿里云会返回 **SignatureDoesNotMatch**(403),与「界面脱敏」不是一回事。
+
+---
+
+## 2. 运行时如何读配置
+
+- 业务统一走 **`internal/oss.LoadConfig()`**:解析 `oss_config`,再应用环境变量兜底,最后校验 bucket / endpoint / 非占位 Secret。
+- **`oss.IsEnabled()`**、通用上传、`upload/image`、`upload/video` 均依赖同一套逻辑。
+
+管理端 **`GET /api/admin/settings`** 中的 `ossConfig`:
+
+- 在 **能成功 `LoadConfig()`** 时,回显 **与实际上传一致的生效字段**(含合并环境变量后的密钥),并设 `secretConfigured: true`。
+- 若 **不能加载有效配置**,则仍以库中原始 JSON 为主,`secretConfigured: false`(常见于库里 Secret 为占位符且未配置环境变量兜底)。
+
+保存:**`POST /api/admin/settings`** 的 `ossConfig` 会合并历史密钥(避免仅因前端未重填 Secret 而覆盖);合并后须能通过 **`oss.ConfigJSONIsReady`** 校验才写入。
+
+---
+
+## 3. 上传策略与路由
+
+**策略(统一)**:若 `LoadConfig()` 成功则 **先上传 OSS**;若 PutObject 失败、返回空 URL,或 **未配置有效 OSS**,则 **自动回退本地磁盘**(`uploads/` 等,由 `UPLOAD_DIR` / 默认目录与 Gin `Static /uploads` 提供访问)。处理函数 **不再因 OSS 失败向客户端返回 5xx**(仍 `success: true` 时),便于线上不因 OSS 瞬时故障中断业务;服务端打 **log** 便于排障。响应 JSON 中常见 **`data.storage`**:`oss` | `local`。
+
+> 说明:配置里曾出现历史环境变量 `UPLOAD_ALLOW_LOCAL_FALLBACK`,**handler 已默认本地兜底**,该开关不再控制是否允许回退(详见 `internal/config/config.go` 注释)。
+
+| 方法 | 路径 | 鉴权 | 说明 |
+|------|------|------|------|
+| POST | `/api/admin/upload` | 管理端 JWT | 通用 multipart:`file` + `folder` |
+| DELETE | `/api/admin/upload` | 管理端 JWT | query `path=`,本地路径或 OSS URL |
+| POST | `/api/upload` | 无硬性要求 | 与 `UploadPost` 同源 |
+| DELETE | `/api/upload` | 视部署 | 同上 |
+| POST | `/api/miniprogram/upload` | 小程序侧约定 | 同源 `UploadPost` |
+| POST | `/api/miniprogram/upload/image` | 同上 | 图片,可压缩;内部 `ossUploadBytes` 再本地 |
+| POST | `/api/miniprogram/upload/video` | 同上 | 视频;内部 `ossUploadFile` 再本地 |
+
+小程序封装见 `miniprogram/utils/miniprogramUpload.js`。**接口侧短表**见 [5、接口/文件上传与OSS.md](../5、接口/文件上传与OSS.md)。
+
+---
+
+## 4. 本地与排障
+
+### 4.1 自检脚本
+
+仓库 **`soul-api/scripts/test_oss_connect.py`**(依赖 `pip install oss2`):
+
+- 默认读取脚本内示例 JSON;**Secret 占位时必须设置环境变量** `OSS_ACCESS_KEY_SECRET` 或 `ALIYUN_OSS_ACCESS_KEY_SECRET`。
+- 成功则对目标 Bucket 执行 `list_objects(max_keys=1)`。
+
+### 4.2 常见现象
+
+| 现象 | 可能原因 |
+|------|----------|
+| `secretConfigured: false` | 库内 Secret 无效/占位;或未设环境变量兜底 |
+| 上传提示未配置 OSS | `LoadConfig()` 失败,查日志 `oss: oss_config rejected` / `parse error` |
+| 管理端 Network 里 Secret 仍是 `****` | 多为 **库里就是字面量 `****`**;修好库或配置 env 并重启后,若 `LoadConfig` 成功,GET settings 会回显生效密钥 |
+
+---
+
+## 5. 安全与运维
+
+- **不要将含真实 OSS Secret 的 `.env` 提交到公开仓库。**
+- 管理端返回完整密钥依赖 **Admin 鉴权**;若接口暴露在公网,须配合 HTTPS、IP 限制与强口令。
+
+---
+
+返回 [6、后端 README](./README.md) · [开发文档索引](../索引.md)
diff --git a/开发文档/ai开发文档.txt b/开发文档/ai开发文档.txt
new file mode 100644
index 00000000..a050aed8
--- /dev/null
+++ b/开发文档/ai开发文档.txt
@@ -0,0 +1,50 @@
+智增增 OpenAI 兼容 API(超级个体 AI 写文章)
+
+基地址:https://api.zhizengzeng.com/v1
+接口:POST /chat/completions(与 OpenAI 一致)
+
+开发文档(模型与价格):
+https://doc.zhizengzeng.com/doc-3979947
+
+密钥:请在智增增控制台创建,写入 soul-api 环境变量(勿明文存入仓库):
+- SUPER_ARTICLE_AI_API_KEY(推荐,与其他 OPENAI 用途隔离)
+- 或 OPENAI_API_KEY
+- SUPER_ARTICLE_AI_MODEL:默认文本与配图均可优先使用(如 gpt-4o-mini,支持 vision)
+- SUPER_ARTICLE_AI_VISION_MODEL:有参考图时自动改用、与图片一并分析的多模态模型(默认 gpt-4o-mini,需在控制台支持 vision)
+
+完整示例见仓库:soul-api/.env.super-article-ai.example
+
+---
+
+以下「写文章规则提示词」由服务端在调用大模型前自动追加在用户提示词与参考素材之后(无须小程序重复传);若需修改规则,请同步改 soul-api/internal/handler/miniprogram_ai_article.go 常量 superArticleWritingRulesSuffix。
+
+写文章规则提示词:
+
+写文章。严格按以下规则成稿。
+
+【材料】用户提供:本场聊天记录/纪要/妙记要点、场次号、主题短句;写作前先按规范阅读飞书运营报表,把报表上有的时长、场观、进房等具体数字写进开篇(只写一次)。
+
+【人称与禁用】叙述全程用「我」,禁止「房主」。禁止「这边」「那边」。禁止「回答说,……」——问句后直接接我的陈述。语气偏强势:干脆、笃定,少「可能/也许」。「卡若」每篇最多 1 次,可完全不出现。
+
+【刺点三对齐】正文第一行标题「# 第X场|短句」、磁盘文件名、开篇刺激性金句三者同一刺点;不写空泛抒情。
+
+【结构与比例】
+- 默认单场 采用「2026 每日派对干货 / 派对体」:三块约 40% / 20%~30% / 余下约 30%;块与块之间必须有过渡口语;每一块结束必须有一段「段末小结」单独成段,30~50 个汉字一句,禁止出现「干货」等标签字。
+
+【开篇】先有冲突或世俗金句,再切入场景。固定数据句式(报表有则写具体数,没有的项不写):「第 X 场,X 分钟;场观 XXXXX,进房 XXX 人。」不写「约」、不写「见报表」。时长/场观/进房仅在开篇出现一次。
+
+【中段】推进用时间线或事件线;多写对话(「X 号问」「有人问」);我的话直接陈述。约在全文 50% 处插入一段浓缩要点 3~6 条(20~40 字/条),前后用 --- 分隔,条目中禁止「干货」二字。
+
+【分享句】约在 20% 处一句、结尾一句,各 ≤50 字,紧扣本节,禁止「干货:」字样。
+
+【强调·派对体成稿必选】Markdown **加粗**:至少 2 句完整立场/结论句(分布在不同段落);至少 3 个不同的重点关键词加粗。禁止堆砌无意义加粗。
+
+【结尾】升华主题;派对体可有文末小结(3~6 条短金句)+ 总结段落 + 一句收尾金句。
+
+【字数与真实性】小节 3000~5000 字;忠于来源不改原意;所有数字必须有依据、写具体算法/金额/人次等,Soul 侧常用口径可参考规范中的曝光/进房/成本写法。
+
+【隐晦植入】文中自然散布 2~4 处:联系管理、做切片、做副业相关一句,不单段喊话,随机10%加率不用每篇文章都加
+
+【版式强制】一句一行,句间空一行;分号也拆成行;段落之间空行;大段堆砌禁止。
+
+【交付】输出 Markdown;标题一行「# 第X场|…」;成稿末尾按模板追加 HTML 注释自检:「我」字数/次数、本场关键字频次、关键字加粗处数、重点句/词加粗是否达标。
diff --git a/开发文档/三端需求业务对齐-小程序与API.md b/开发文档/三端需求业务对齐-小程序与API.md
index 0ac9eb43..7722bb96 100644
--- a/开发文档/三端需求业务对齐-小程序与API.md
+++ b/开发文档/三端需求业务对齐-小程序与API.md
@@ -133,6 +133,10 @@
/api/miniprogram/ckb/join
/api/miniprogram/ckb/match ← 已挂载
/api/miniprogram/upload
+/api/miniprogram/my/super-stats
+/api/miniprogram/super/articles ← GET 列表、POST 发布
+/api/miniprogram/super/articles/:id ← GET 详情
+/api/miniprogram/super/articles/generate ← POST AI 生成草稿(参考 开发文档/5、接口/API接口完整文档.md §1.3)
/api/miniprogram/user/addresses, addresses/:id
/api/miniprogram/user/check-purchased, profile, purchase-status, reading-progress, update
/api/miniprogram/withdraw, withdraw/records, pending-confirm, confirm-received, confirm-info
diff --git a/开发文档/索引.md b/开发文档/索引.md
index 0849dcf2..e0bcb4e6 100644
--- a/开发文档/索引.md
+++ b/开发文档/索引.md
@@ -1,6 +1,6 @@
# Soul 创业派对 - 开发文档索引
-> **以代码为准**:需求或实现变更时同步更新本文档与对应章节 README。最后更新:2026-04-07
+> **以代码为准**:需求或实现变更时同步更新本文档与对应章节 README。最后更新:2026-05-09
---
@@ -99,7 +99,7 @@
| [API接口完整文档.md](./5、接口/API接口完整文档.md) | **Soul 项目 API 主真源** |
| [配置清单-完整版.md](./5、接口/配置清单-完整版.md) | 配置项清单 |
| [在线支付对接文档.md](./5、接口/在线支付对接文档.md) | 支付 |
-| [接口与提现.md](./5、接口/接口与提现.md) | 提现相关接口说明 |
+| [文件上传与OSS.md](./5、接口/文件上传与OSS.md) | 路由摘要,`data.storage`,互链后端详版 |
---
@@ -108,6 +108,7 @@
| 文件/路径 | 用途 |
|-----------|------|
| [README.md](./6、后端/README.md) | 本目录索引 |
+| [**阿里云OSS与文件上传.md**](./6、后端/阿里云OSS与文件上传.md) | **OSS、上传策略、路由、env 兜底、`storage`、`UPLOAD_*`、自检脚本** |
| [后端架构.md](./6、后端/后端架构.md) | soul-api 架构 |
| [后端开发规范.md](./6、后端/后端开发规范.md) | 编码与路由约定 |
| [内容创建问题修复说明.md](./6、后端/内容创建问题修复说明.md) | 专项修复说明 |