feat: 内容管理第5批优化 - Bug修复 + 分享功能 + 代付功能
1. Bug修复: - 修复Markdown星号/下划线在小程序端原样显示问题(markdownToHtml增加__和_支持,contentParser增加Markdown格式剥离) - 修复@提及无反应(MentionSuggestion使用ref保持persons最新值,解决闭包捕获空数组问题) - 修复#链接标签点击"未找到小程序配置"(增加appId直接跳转降级路径) 2. 分享功能优化: - "分享到朋友圈"改为"分享给好友"(open-type从shareTimeline改为share) - 90%收益提示移到分享按钮下方 - 阅读20%后向上滑动弹出分享浮层提示(4秒自动消失) 3. 代付功能: - 后端:新增UserBalance/BalanceTransaction/GiftUnlock三个模型 - 后端:新增8个余额相关API(查询/充值/充值确认/代付/领取/退款/交易记录/礼物信息) - 小程序:阅读页新增"代付分享"按钮,支持用余额为好友解锁章节 - 分享链接携带gift参数,好友打开自动领取解锁 Made-with: Cursor
This commit is contained in:
4
scripts/.env.feishu
Normal file
4
scripts/.env.feishu
Normal file
@@ -0,0 +1,4 @@
|
||||
# 飞书应用凭证(卡若私域)- 勿提交到公开仓库
|
||||
FEISHU_APP_ID=cli_a48818290ef8100d
|
||||
FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4
|
||||
FEISHU_WIKI_NODE_TOKEN=FNP6wdvNKij7yMkb3xCce0CYnpd
|
||||
75
scripts/fix_duplicate_title_upload.py
Normal file
75
scripts/fix_duplicate_title_upload.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""批量去掉重复标题并重新上传到小程序。content_upload 已内置 strip 首行 # 标题。"""
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
BASE_2026 = Path("/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/2026每日派对干货")
|
||||
BASE_9 = Path("/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章|我在Soul上亲访的赚钱案例")
|
||||
|
||||
# (id, title, md_file_path relative to BASE_2026 or "9" for BASE_9)
|
||||
CHAPTERS_2026 = [
|
||||
("10.01", "第102场|今年第一个红包你发给谁", BASE_2026 / "第102场|今年第一个红包你发给谁.md"),
|
||||
("10.02", "第103场|号商、某客与炸房", BASE_2026 / "第103场|号商、某客与炸房.md"),
|
||||
("10.03", "第105场|创业社群、直播带货与程序员", BASE_2026 / "第105场|创业社群、直播带货与程序员.md"),
|
||||
("10.04", "第104场|婚恋、AI客服与一个微信", BASE_2026 / "第104场|婚恋、AI客服与一个微信.md"),
|
||||
("10.05", "第107场|性格、陪伴经济与本地AI", BASE_2026 / "第107场|性格、陪伴经济与本地AI.md"),
|
||||
("10.06", "第108场|Soul场观400等于抖音1万", BASE_2026 / "第108场|Soul场观400等于抖音1万.md"),
|
||||
("10.07", "第111场|平台规则变了怎么办", BASE_2026 / "第111场|平台规则变了怎么办.md"),
|
||||
("10.08", "第110场|Soul变现逻辑全程公开", BASE_2026 / "第110场|Soul变现逻辑全程公开.md"),
|
||||
("10.09", "第112场|一个人起头,维权挣了大半套房", BASE_2026 / "第112场|一个人起头,维权挣了大半套房.md"),
|
||||
("10.10", "第113场|不会选择怎么办?", BASE_2026 / "第113场|不会选择怎么办?.md"),
|
||||
("10.11", "第114场|人跟人差别,以前没 AI 差 100 倍,有 AI 差 1 万倍。", BASE_2026 / "第114场-人跟人差别,以前没 AI 差 100 倍,有 AI 差 1 万倍。.md"),
|
||||
("10.12", "第115场|一天改变,可控的事先做", BASE_2026 / "第115场|一天改变,可控的事先做.md"),
|
||||
("10.13", "第116场|钱是大风刮来的,怎么抓住?", BASE_2026 / "第116场|钱是大风刮来的,怎么抓住?.md"),
|
||||
("10.14", "第117场|流水百万挣八千,你还干不干?", BASE_2026 / "第117场|流水百万挣八千,你还干不干?.md"),
|
||||
("10.15", "第118场|运气是选出来的,不是等出来的", BASE_2026 / "第118场|运气是选出来的,不是等出来的.md"),
|
||||
("10.16", "第119场|开派对的初心是早上不影响老婆睡觉", BASE_2026 / "第119场|开派对的初心是早上不影响老婆睡觉.md"),
|
||||
("10.17", "第120场|发视频就有钱,这才是最低门槛的AI副业", BASE_2026 / "第120场|发视频就有钱,这才是最低门槛的AI副业.md"),
|
||||
]
|
||||
|
||||
CHAPTERS_9 = [
|
||||
("9.15", "派对副业|做切片分发和副业分发的具体步骤与收益", BASE_9 / "9.15 派对副业.md"),
|
||||
("9.16", "如何开Soul派对|房主避坑、流量、变现与封号", BASE_9 / "9.16 如何开Soul派对|房主避坑、流量、变现与封号.md"),
|
||||
]
|
||||
|
||||
|
||||
def run_upload(section_id: str, title: str, content_file: Path, part: str, chapter: str) -> bool:
|
||||
if not content_file.exists():
|
||||
print(f" 跳过 {section_id}: 文件不存在 {content_file}")
|
||||
return False
|
||||
cmd = [
|
||||
sys.executable,
|
||||
str(PROJECT_ROOT / "content_upload.py"),
|
||||
"--id", section_id,
|
||||
"--title", title,
|
||||
"--content-file", str(content_file),
|
||||
"--part", part,
|
||||
"--chapter", chapter,
|
||||
"--price", "1.0",
|
||||
]
|
||||
r = subprocess.run(cmd, cwd=str(PROJECT_ROOT))
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
def main():
|
||||
ok, fail = 0, 0
|
||||
for sid, title, fpath in CHAPTERS_2026:
|
||||
print(f"上传 {sid} {title[:30]}...")
|
||||
if run_upload(sid, title, fpath, "part-2026-daily", "chapter-2026-daily"):
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
for sid, title, fpath in CHAPTERS_9:
|
||||
print(f"上传 {sid} {title[:30]}...")
|
||||
if run_upload(sid, title, fpath, "part-4", "chapter-9"):
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
print(f"\n完成: 成功 {ok}, 失败 {fail}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
151
scripts/migrate_2026_sections.py
Normal file
151
scripts/migrate_2026_sections.py
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将第102场及以后的派对场次迁移到「2026每日派对干货」目录
|
||||
|
||||
用法:
|
||||
python3 scripts/migrate_2026_sections.py # 仅预览,不执行
|
||||
python3 scripts/migrate_2026_sections.py --execute # 执行迁移
|
||||
|
||||
迁移规则:
|
||||
- 从章节中筛选 section_title 包含「第102场」「第103场」... 的条目
|
||||
- 按场次号排序,依次赋 id 10.01, 10.02, 10.03, ...
|
||||
- 更新 part_id=part-2026-daily, part_title=2026每日派对干货
|
||||
- 更新 chapter_id=chapter-2026-daily, chapter_title=2026每日派对干货
|
||||
|
||||
依赖: pip install pymysql
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 项目根目录
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
try:
|
||||
import pymysql
|
||||
except ImportError:
|
||||
print("需要安装 pymysql: pip3 install pymysql")
|
||||
sys.exit(1)
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": "56b4c23f6853c.gz.cdb.myqcloud.com",
|
||||
"port": 14413,
|
||||
"user": "cdb_outerroot",
|
||||
"password": "Zhiqun1984",
|
||||
"database": "soul_miniprogram",
|
||||
"charset": "utf8mb4",
|
||||
}
|
||||
|
||||
PART_2026 = "part-2026-daily"
|
||||
CHAPTER_2026 = "chapter-2026-daily"
|
||||
TITLE_2026 = "2026每日派对干货"
|
||||
|
||||
|
||||
def extract_session_num(section_title: str) -> int | None:
|
||||
"""从 section_title 解析场次号,如 第102场 -> 102"""
|
||||
m = re.search(r"第(\d+)场", section_title)
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
|
||||
def get_connection():
|
||||
return pymysql.connect(**DB_CONFIG)
|
||||
|
||||
|
||||
def get_max_10_section(cur) -> int:
|
||||
"""获取当前 10.xx 最大序号"""
|
||||
cur.execute(
|
||||
"SELECT id FROM chapters WHERE id REGEXP '^10\\.[0-9]+$' ORDER BY CAST(SUBSTRING_INDEX(id, '.', -1) AS UNSIGNED) DESC LIMIT 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return int(row[0].split(".")[-1])
|
||||
return 0
|
||||
|
||||
|
||||
def find_sections_to_migrate(cur) -> list[tuple]:
|
||||
"""查找需要迁移的章节:第102场及以后,且不在 part-2026-daily 的"""
|
||||
cur.execute("""
|
||||
SELECT id, section_title, part_id, chapter_id, sort_order
|
||||
FROM chapters
|
||||
WHERE section_title REGEXP '第[0-9]+场'
|
||||
ORDER BY sort_order, id
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
to_migrate = []
|
||||
for row in rows:
|
||||
sid, title, part_id, ch_id, order = row
|
||||
num = extract_session_num(title)
|
||||
if num is not None and num >= 102 and part_id != PART_2026:
|
||||
to_migrate.append((sid, title, part_id, ch_id, order, num))
|
||||
to_migrate.sort(key=lambda x: (x[5], x[4])) # 按场次号、sort_order
|
||||
return to_migrate
|
||||
|
||||
|
||||
def run(dry_run: bool):
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
rows = find_sections_to_migrate(cur)
|
||||
max_10 = get_max_10_section(cur)
|
||||
conn.close()
|
||||
|
||||
if not rows:
|
||||
print("未找到需要迁移的章节(第102场及以后、且不在 2026每日派对干货 中)")
|
||||
return
|
||||
|
||||
print(f"当前 10.xx 最大序号: {max_10}")
|
||||
print(f"找到 {len(rows)} 节待迁移到「2026每日派对干货」:\n")
|
||||
plan = []
|
||||
for i, (old_id, title, part_id, ch_id, order, num) in enumerate(rows, 1):
|
||||
new_id = f"10.{max_10 + i:02d}"
|
||||
plan.append((old_id, new_id, title, part_id))
|
||||
print(f" {old_id} -> {new_id} {title}")
|
||||
|
||||
if dry_run:
|
||||
print("\n[预览模式] 未执行写入,使用 --execute 执行迁移")
|
||||
return
|
||||
|
||||
print("\n执行迁移...")
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
try:
|
||||
# 先全部改为临时 id(避免与已有 10.xx 冲突)
|
||||
for i, (old_id, new_id, title, part_id) in enumerate(plan, 1):
|
||||
tmp_id = f"tmp-migrate-{old_id.replace('.', '-')}"
|
||||
cur.execute(
|
||||
"UPDATE chapters SET id = %s WHERE id = %s",
|
||||
(tmp_id, old_id),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# 再改为最终 id 并更新 part/chapter
|
||||
for i, (old_id, new_id, title, part_id) in enumerate(plan, 1):
|
||||
tmp_id = f"tmp-migrate-{old_id.replace('.', '-')}"
|
||||
cur.execute("""
|
||||
UPDATE chapters SET
|
||||
id = %s, part_id = %s, part_title = %s,
|
||||
chapter_id = %s, chapter_title = %s
|
||||
WHERE id = %s
|
||||
""", (new_id, PART_2026, TITLE_2026, CHAPTER_2026, TITLE_2026, tmp_id))
|
||||
conn.commit()
|
||||
print(f"已迁移 {len(plan)} 节到 part-2026-daily,id 为 10.01 ~ 10.{len(plan):02d}")
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"迁移失败: {e}")
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="将第102场及以后的场次迁移到 2026每日派对干货")
|
||||
parser.add_argument("--execute", action="store_true", help="执行迁移(默认仅预览)")
|
||||
args = parser.parse_args()
|
||||
run(dry_run=not args.execute)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -13,7 +13,7 @@ from urllib.request import Request, urlopen
|
||||
|
||||
CONFIG_PATH = Path(__file__).resolve().parent / "feishu_publish_config.json"
|
||||
WEBHOOK_ENV = "FEISHU_KARUO_LOG_WEBHOOK"
|
||||
DEFAULT_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/8b7f996e-2892-4075-989f-aa5593ea4fbc"
|
||||
DEFAULT_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/34b762fc-5b9b-4abb-a05a-96c8fb9599f1"
|
||||
WIKI_URL = "https://cunkebao.feishu.cn/wiki/FNP6wdvNKij7yMkb3xCce0CYnpd"
|
||||
MINIPROGRAM_BASE = "https://soul.quwanzhi.com/read"
|
||||
MATERIAL_HINT = "材料找卡若AI拿"
|
||||
|
||||
@@ -31,7 +31,7 @@ except ImportError:
|
||||
# 与 post_to_feishu 保持一致
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
# 默认发到 Soul 彩民团队飞书群
|
||||
WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/14a7e0d3-864d-4709-ad40-0def6edba566"
|
||||
WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/34b762fc-5b9b-4abb-a05a-96c8fb9599f1"
|
||||
BACKEND_QRCODE_URL = "https://soul.quwanzhi.com/api/miniprogram/qrcode"
|
||||
MINIPROGRAM_READ_BASE = "https://soul.quwanzhi.com/read"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user