feat: 小程序超级个体/个人资料/CKB获客;VIP列表展示过滤;管理端与API联调
- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
This commit is contained in:
237
content_upload.py
Normal file
237
content_upload.py
Normal file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
将书稿 md 上传到小程序对应 chapters 表(与 Soul创业实验 Skill「上传」一致)。
|
||||
|
||||
依赖: pip install pymysql
|
||||
数据库配置:复用 scripts/migrate_2026_sections.py 中的 DB_CONFIG(与现网一致)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
PART_2026 = "part-2026-daily"
|
||||
CHAPTER_2026 = "chapter-2026-daily"
|
||||
TITLE_2026 = "2026每日派对干货"
|
||||
|
||||
|
||||
def load_db_config() -> dict:
|
||||
mig = ROOT / "scripts" / "migrate_2026_sections.py"
|
||||
if not mig.is_file():
|
||||
print("缺少 scripts/migrate_2026_sections.py,无法读取 DB_CONFIG", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
spec = importlib.util.spec_from_file_location("_mig_db", mig)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(mod)
|
||||
cfg = getattr(mod, "DB_CONFIG", None)
|
||||
if not isinstance(cfg, dict):
|
||||
print("migrate_2026_sections.py 中无有效 DB_CONFIG", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return cfg
|
||||
|
||||
|
||||
def strip_md_title_line(text: str) -> str:
|
||||
lines = text.splitlines()
|
||||
if lines and lines[0].lstrip().startswith("#"):
|
||||
return "\n".join(lines[1:]).lstrip("\n")
|
||||
return text
|
||||
|
||||
|
||||
def for_miniprogram_body(text: str) -> str:
|
||||
"""上传 README:少用 --- 分割线;正文内独立一行的 --- 改为空行分段。"""
|
||||
out_lines: list[str] = []
|
||||
for line in text.splitlines():
|
||||
if line.strip() == "---":
|
||||
out_lines.append("")
|
||||
out_lines.append("")
|
||||
else:
|
||||
out_lines.append(line)
|
||||
return "\n".join(out_lines).strip() + "\n"
|
||||
|
||||
|
||||
def next_10_id(cur) -> str:
|
||||
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 not row:
|
||||
return "10.01"
|
||||
last = row[0]
|
||||
n = int(last.split(".")[-1])
|
||||
return f"10.{n + 1:02d}"
|
||||
|
||||
|
||||
def list_structure(cur):
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT DISTINCT part_id, part_title, chapter_id, chapter_title
|
||||
FROM chapters
|
||||
ORDER BY part_id, chapter_id
|
||||
"""
|
||||
)
|
||||
print("篇章结构(distinct part/chapter):")
|
||||
for r in cur.fetchall():
|
||||
print(f" part={r[0]!r} chapter={r[2]!r} | {r[1]} / {r[3]}")
|
||||
|
||||
|
||||
def list_chapters_2026(cur):
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, section_title, sort_order
|
||||
FROM chapters
|
||||
WHERE part_id = %s AND chapter_id = %s
|
||||
ORDER BY COALESCE(sort_order, 999999) ASC, id ASC
|
||||
""",
|
||||
(PART_2026, CHAPTER_2026),
|
||||
)
|
||||
print(f"2026每日派对干货 ({PART_2026} / {CHAPTER_2026}):")
|
||||
for r in cur.fetchall():
|
||||
print(f" {r[0]}\torder={r[2]}\t{r[1]}")
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
import pymysql
|
||||
except ImportError:
|
||||
print("需要: pip install pymysql", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
p = argparse.ArgumentParser(description="上传书稿 md 到 soul_miniprogram.chapters")
|
||||
p.add_argument("--id", help="业务 id,如 10.27;省略则自动取当前最大 10.xx +1")
|
||||
p.add_argument("--title", help="小节标题,如 第128场|主题")
|
||||
p.add_argument("--content-file", type=Path, help="文章 md 绝对或相对路径")
|
||||
p.add_argument("--part", default=PART_2026)
|
||||
p.add_argument("--chapter", default=CHAPTER_2026)
|
||||
p.add_argument("--part-title", default=TITLE_2026)
|
||||
p.add_argument("--chapter-title", default=TITLE_2026)
|
||||
p.add_argument("--price", type=float, default=1.0)
|
||||
p.add_argument("--free", action="store_true", help="标记为免费")
|
||||
p.add_argument("--list-structure", action="store_true")
|
||||
p.add_argument("--list-chapters", action="store_true")
|
||||
p.add_argument("--dry-run", action="store_true")
|
||||
args = p.parse_args()
|
||||
|
||||
cfg = load_db_config()
|
||||
conn = pymysql.connect(**cfg)
|
||||
cur = conn.cursor()
|
||||
|
||||
if args.list_structure:
|
||||
list_structure(cur)
|
||||
conn.close()
|
||||
return
|
||||
if args.list_chapters:
|
||||
list_chapters_2026(cur)
|
||||
conn.close()
|
||||
return
|
||||
|
||||
if not args.title or not args.content_file:
|
||||
p.error("上传时必须提供 --title 与 --content-file")
|
||||
|
||||
path = args.content_file.expanduser().resolve()
|
||||
if not path.is_file():
|
||||
print(f"文件不存在: {path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
body = for_miniprogram_body(strip_md_title_line(raw))
|
||||
word_count = len(body)
|
||||
is_free = 1 if args.free else 0
|
||||
price = 0.0 if args.free else float(args.price)
|
||||
|
||||
section_id = args.id
|
||||
if not section_id:
|
||||
section_id = next_10_id(cur)
|
||||
print(f"未指定 --id,使用新 id: {section_id}")
|
||||
|
||||
cur.execute("SELECT mid FROM chapters WHERE id = %s", (section_id,))
|
||||
row = cur.fetchone()
|
||||
exists = row is not None
|
||||
|
||||
cur.execute("SELECT COALESCE(MAX(sort_order), -1) FROM chapters")
|
||||
max_sort = cur.fetchone()[0]
|
||||
next_sort = int(max_sort) + 1
|
||||
|
||||
if args.dry_run:
|
||||
print(f"id={section_id} exists={exists} next_sort={next_sort} words={word_count}")
|
||||
print(body[:500] + ("..." if len(body) > 500 else ""))
|
||||
conn.close()
|
||||
return
|
||||
|
||||
if exists:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE chapters SET
|
||||
section_title = %s,
|
||||
content = %s,
|
||||
word_count = %s,
|
||||
price = %s,
|
||||
is_free = %s,
|
||||
part_id = %s,
|
||||
part_title = %s,
|
||||
chapter_id = %s,
|
||||
chapter_title = %s,
|
||||
updated_at = NOW()
|
||||
WHERE id = %s
|
||||
""",
|
||||
(
|
||||
args.title,
|
||||
body,
|
||||
word_count,
|
||||
price,
|
||||
is_free,
|
||||
args.part,
|
||||
args.part_title,
|
||||
args.chapter,
|
||||
args.chapter_title,
|
||||
section_id,
|
||||
),
|
||||
)
|
||||
print(f"已更新 {section_id} | {args.title}")
|
||||
else:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO chapters (
|
||||
id, part_id, part_title, chapter_id, chapter_title,
|
||||
section_title, content, word_count, is_free, price,
|
||||
sort_order, status, edition_standard, edition_premium,
|
||||
hot_score, created_at, updated_at
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s,
|
||||
%s, 'published', 1, 0,
|
||||
0, NOW(), NOW()
|
||||
)
|
||||
""",
|
||||
(
|
||||
section_id,
|
||||
args.part,
|
||||
args.part_title,
|
||||
args.chapter,
|
||||
args.chapter_title,
|
||||
args.title,
|
||||
body,
|
||||
word_count,
|
||||
is_free,
|
||||
price,
|
||||
next_sort,
|
||||
),
|
||||
)
|
||||
print(f"已创建 {section_id} | {args.title} | sort_order={next_sort}")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user