#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
上传《一人公司——你能掌控的自由人生》到小程序。
book_key : solo-company
共 10 章 + 1 术语手册,分 4 个篇。
用法:
python3 scripts/upload_solo_company_book.py --dry-run # 预览
python3 scripts/upload_solo_company_book.py # 执行上传
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
BOOK_DIR = Path("/Users/karuo/Documents/个人/2、我写的书/《一人公司》")
BOOK_KEY = "solo-company"
BOOK_TITLE = "一人公司"
BOOK_SUBTITLE = "你能掌控的自由人生"
BOOK_EMOJI = "🏢"
BOOK_SORT_ORDER = 10
# 篇章结构定义
PARTS = {
"part-solo-1": {
"title": "启程·方向",
"chapters": {
"chapter-solo-1": {
"title": "找准方向,别再瞎忙",
"sections": [1, 2, 3],
}
},
},
"part-solo-2": {
"title": "实战·方法",
"chapters": {
"chapter-solo-2": {
"title": "用对方法,高效变现",
"sections": [4, 5, 6],
}
},
},
"part-solo-3": {
"title": "进阶·系统",
"chapters": {
"chapter-solo-3": {
"title": "搭建系统,持续增长",
"sections": [7, 8, 9, 10],
}
},
},
"part-solo-appendix": {
"title": "附录",
"chapters": {
"ch-solo-appendix": {
"title": "术语手册",
"sections": ["glossary"],
}
},
},
}
# 章节文件映射
SECTION_FILES = {
1: "1、你越努力越穷,是因为你连方向都没找对.md",
2: "2、9个人干出千万业绩:一人公司到底怎么运转的.md",
3: "3、别再找项目了——你自己就是最值钱的产品.md",
4: "4、我从不追求原创,但我每篇内容都赚到了钱.md",
5: "5、30个账号每天自动来客户——你也能复制这套打法.md",
6: "6、你认识谁,比你会什么值钱10倍.md",
7: "7、搭一台你睡着了也在赚钱的机器.md",
8: "8、你赚多少钱,取决于你把自己当成什么人.md",
9: "9、一天重启你的人生:我用了10年验证的方法.md",
10: "10、别等准备好再开始——这游戏根本没有终点.md",
"glossary": "术语手册:这本书里的黑话全在这.md",
}
# 章节标题(去掉序号前缀)
SECTION_TITLES = {
1: "你越努力越穷,是因为你连方向都没找对",
2: "9个人干出千万业绩:一人公司到底怎么运转的",
3: "别再找项目了——你自己就是最值钱的产品",
4: "我从不追求原创,但我每篇内容都赚到了钱",
5: "30个账号每天自动来客户——你也能复制这套打法",
6: "你认识谁,比你会什么值钱10倍",
7: "搭一台你睡着了也在赚钱的机器",
8: "你赚多少钱,取决于你把自己当成什么人",
9: "一天重启你的人生:我用了10年验证的方法",
10: "别等准备好再开始——这游戏根本没有终点",
"glossary": "术语手册:这本书里的黑话全在这",
}
DB_CONFIG = {
"host": "56b4c23f6853c.gz.cdb.myqcloud.com",
"port": 14413,
"user": "cdb_outerroot",
"password": "Zhiqun1984",
"database": "soul_miniprogram",
}
def md_to_html(md_text: str) -> str:
"""将 Markdown 转为美观的 HTML(适配小程序 contentParser)"""
lines = md_text.splitlines()
# 去掉第一行 # 标题
if lines and lines[0].lstrip().startswith("# "):
lines = lines[1:]
html_parts: list[str] = []
i = 0
def inline_format(text: str) -> str:
"""处理行内格式:粗体、斜体、行内代码、链接"""
# 粗体+斜体
text = re.sub(r"\*\*\*(.+?)\*\*\*", r"\1", text)
# 粗体
text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
# 斜体
text = re.sub(r"(?\1", text)
# 行内代码
text = re.sub(r"`(.+?)`", r"\1", text)
# 链接
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'\1', text)
return text
while i < len(lines):
line = lines[i]
stripped = line.strip()
# 空行跳过
if not stripped:
i += 1
continue
# 图片 → 跳过(小程序不支持外链图片)
if re.match(r"^!\[.*\]\(.*\)$", stripped):
i += 1
continue
# 水平线
if stripped == "---":
html_parts.append("
{'") continue # 待办列表 - [ ] / - [x] if re.match(r"^- \[[ x]\] ", stripped): items = [] while i < len(lines) and re.match(r"^- \[[ x]\] ", lines[i].strip()): s = lines[i].strip() checked = s[3] == "x" text = inline_format(s[6:].strip()) check_mark = "✅" if checked else "☐" items.append(f"
'.join(quote_lines)}
{para_lines[0]}
") continue return "\n".join(html_parts) def find_section_location(section_key) -> tuple[str, str, str, str]: """根据 section key 找到对应的 part/chapter 信息""" for part_id, part_info in PARTS.items(): for ch_id, ch_info in part_info["chapters"].items(): if section_key in ch_info["sections"]: return part_id, part_info["title"], ch_id, ch_info["title"] return "part-solo-1", "启程·方向", "chapter-solo-1", "找准方向,别再瞎忙" def main(): try: import pymysql except ImportError: print("需要: pip install pymysql", file=sys.stderr) sys.exit(1) dry_run = "--dry-run" in sys.argv price_free = 0.0 price_paid = 1.0 conn = pymysql.connect(**DB_CONFIG) cur = conn.cursor() # ── Step 1: 确保书籍存在 ── cur.execute("SELECT id FROM books WHERE book_key = %s", (BOOK_KEY,)) row = cur.fetchone() if row: book_id = row[0] print(f"📖 书籍已存在: id={book_id}, key={BOOK_KEY}") if not dry_run: cur.execute( "UPDATE books SET title=%s, subtitle=%s, icon_emoji=%s, sort_order=%s, status='published' WHERE id=%s", (BOOK_TITLE, BOOK_SUBTITLE, BOOK_EMOJI, BOOK_SORT_ORDER, book_id), ) conn.commit() else: if dry_run: print(f"📖 [dry] 将创建书籍: {BOOK_KEY} - {BOOK_TITLE}") book_id = 999 else: cur.execute( "INSERT INTO books (book_key, title, subtitle, icon_emoji, sort_order, status) VALUES (%s, %s, %s, %s, %s, 'published')", (BOOK_KEY, BOOK_TITLE, BOOK_SUBTITLE, BOOK_EMOJI, BOOK_SORT_ORDER), ) conn.commit() book_id = cur.lastrowid print(f"📖 已创建书籍: id={book_id}, key={BOOK_KEY}") # ── Step 2: 上传各章节 ── total_ok = 0 total_err = 0 sort_order = 0 for section_key in list(range(1, 11)) + ["glossary"]: filename = SECTION_FILES[section_key] title = SECTION_TITLES[section_key] section_id = f"solo-{section_key}" if isinstance(section_key, int) else "solo-glossary" part_id, part_title, chapter_id, chapter_title = find_section_location(section_key) filepath = BOOK_DIR / filename if not filepath.exists(): print(f" ⚠ 文件不存在: {filepath}") total_err += 1 continue raw = filepath.read_text(encoding="utf-8") html_content = md_to_html(raw) word_count = len(re.sub(r"<[^>]+>", "", html_content)) is_free = section_key in [1, "glossary"] price = price_free if is_free else price_paid if dry_run: preview = html_content[:80].replace("\n", " ") print(f" [dry] {section_id:16s} | {title[:35]:35s} | {word_count:5d}字 | {'免费' if is_free else '付费'} | {preview}…") total_ok += 1 sort_order += 1 continue try: cur.execute("SELECT mid FROM chapters WHERE id = %s", (section_id,)) existing = cur.fetchone() if existing: cur.execute( """UPDATE chapters SET book_id=%s, section_title=%s, content=%s, word_count=%s, part_id=%s, part_title=%s, chapter_id=%s, chapter_title=%s, price=%s, is_free=%s, sort_order=%s, status='published', edition_standard=1, edition_premium=0 WHERE id=%s""", (book_id, title, html_content, word_count, part_id, part_title, chapter_id, chapter_title, price, is_free, sort_order, section_id), ) conn.commit() print(f" ✓ [updated] {section_id:16s} | {title[:35]:35s} | {word_count:5d}字") else: cur.execute( """INSERT INTO chapters (id, book_id, section_title, content, word_count, part_id, part_title, chapter_id, chapter_title, price, is_free, sort_order, status, edition_standard, edition_premium) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'published', 1, 0)""", (section_id, book_id, title, html_content, word_count, part_id, part_title, chapter_id, chapter_title, price, is_free, sort_order), ) conn.commit() print(f" ✓ [created] {section_id:16s} | {title[:35]:35s} | {word_count:5d}字") total_ok += 1 except Exception as e: conn.rollback() print(f" ✗ 失败 {section_id}: {e}") total_err += 1 sort_order += 1 conn.close() print(f"\n{'='*60}") print(f"完成:成功 {total_ok} | 失败 {total_err}") if dry_run: print("(dry-run 模式,未实际写库)") else: print("✅ 上传完毕!请在管理端或小程序验证。") if __name__ == "__main__": main()