Files
vps_web/seed_forum_demo.py
ddrwode 036a19f28c 哈哈
2026-02-10 13:48:58 +08:00

657 lines
27 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""批量生成论坛演示内容:用户、帖子、评论、点赞、收藏。"""
from datetime import datetime, timedelta, timezone
from app import app, db
from models import User, ForumPost, ForumComment, ForumCategory, ForumPostLike, ForumPostBookmark
DEFAULT_PASSWORD = "Forum123456"
USERS = [
{"username": "ops_alan", "password": DEFAULT_PASSWORD},
{"username": "dev_yuki", "password": DEFAULT_PASSWORD},
{"username": "cloud_nana", "password": DEFAULT_PASSWORD},
{"username": "linux_mason", "password": DEFAULT_PASSWORD},
{"username": "sec_neo", "password": DEFAULT_PASSWORD},
{"username": "ai_rookie", "password": DEFAULT_PASSWORD},
]
POSTS = [
{
"title": "Ubuntu 22.04 安装宝塔面板后的 10 项安全加固(实战清单)",
"author": "ops_alan",
"category": "运维经验",
"days_ago": 7,
"is_featured": True,
"content": """背景:很多同学装完宝塔就直接上线,结果 2-3 天就被扫端口、爆破后台。
我自己在生产环境里执行的加固顺序如下,基本可以在 30 分钟内做完:
1. 安装后第一时间修改宝塔面板入口和端口,不使用默认路径。
2. 宝塔后台开启登录限制(失败次数锁定 + 验证码 + 仅允许白名单 IP
3. SSH 禁止 root 密码登录,只保留密钥登录;同时改 SSH 端口并记录在资产台账。
4. 开启 UFW / firewalld只放行必须端口22/80/443其余全部拒绝。
5. 云厂商安全组与系统防火墙双层控制,避免“安全组放行过宽”。
6. 删除不必要的默认站点,避免目录遍历和弱口令后台暴露。
7. 数据库不要对公网开放 3306应用与数据库尽量走内网。
8. 配置定时备份(站点、数据库、配置文件),并异地存储。
9. 安装 fail2ban对 SSH 和 Web 登录做自动封禁。
10. 上线后立即做一次端口扫描与弱口令自检,确认暴露面。
建议:宝塔是效率工具,不是安全工具。上线前至少做一次“攻击面最小化”检查。""",
"comments": [
("sec_neo", "这个清单很实用,尤其是 SSH 密钥登录 + fail2ban很多人会忽略。"),
("cloud_nana", "补充一点:最好把宝塔登录入口放到仅内网访问,再通过跳板机管理。"),
],
"likes": ["dev_yuki", "cloud_nana", "linux_mason", "sec_neo"],
"bookmarks": ["dev_yuki", "ai_rookie", "linux_mason"],
},
{
"title": "服务器开放端口的正确姿势:安全组、防火墙、服务监听三层排查",
"author": "sec_neo",
"category": "运维经验",
"days_ago": 6,
"content": """很多“端口开了但访问不到”的问题,本质是三层中有一层没打通。
排查顺序建议固定为:
第一层:云安全组
- 入站规则是否允许目标端口(如 80/443
- 来源 CIDR 是否过于严格(如只放了办公网);
- 是否误把规则加在了错误的网卡或实例组。
第二层:系统防火墙
- `ufw status` / `firewall-cmd --list-all` 检查是否放行;
- 是否有默认拒绝策略导致端口被拦;
- 改完记得 reload。
第三层:服务监听
- `ss -lntp | grep 80` 检查进程是否监听;
- 注意监听地址是 `0.0.0.0` 还是 `127.0.0.1`
- 应用容器端口映射是否正确Docker 常见问题)。
最后一步:
- 本机 `curl 127.0.0.1:端口`
- 内网 `curl 内网IP:端口`
- 外网 `curl 公网IP:端口`。
这样基本 10-15 分钟能定位到具体环节。""",
"comments": [
("ops_alan", "三层模型非常清晰,新手排障直接照这个顺序来就行。"),
("ai_rookie", "我之前就是服务只监听 127.0.0.1,难怪外网怎么都不通。"),
],
"likes": ["ops_alan", "dev_yuki", "ai_rookie"],
"bookmarks": ["ops_alan", "cloud_nana", "ai_rookie"],
},
{
"title": "Nginx 绑定域名 + HTTPS 全流程(含 301、HSTS、自动续期",
"author": "cloud_nana",
"category": "运维经验",
"days_ago": 5,
"is_featured": True,
"content": """这篇给一个可直接落地的域名绑定流程,适合新站上线:
步骤 1DNS 解析
- A 记录指向服务器公网 IP
- 等待生效(通常 1-10 分钟,部分服务商更久)。
步骤 2Nginx server block
- `server_name` 填主域名 + www
- 先用 HTTP 跑通站点,不要一开始就上证书。
步骤 3申请证书Let's Encrypt
- 推荐 certbot
- 申请成功后检查证书路径和 Nginx 引用是否一致。
步骤 4强制 HTTPS
- 80 端口统一做 301 跳转到 https
- 保留 ACME 验证路径例外(如果你用 webroot 方式)。
步骤 5安全头
- 开启 HSTS先短时间再逐步拉长
- 可补充 `X-Content-Type-Options`、`X-Frame-Options`。
步骤 6自动续期
- `certbot renew --dry-run` 先验证;
- 用 systemd timer / crontab 定期续期并 reload Nginx。
上线后建议在 SSL Labs 跑一遍,确保协议和套件配置达标。""",
"comments": [
("linux_mason", "赞同先 HTTP 再 HTTPS很多人一上来配证书容易定位困难。"),
("sec_neo", "HSTS 建议分阶段,别一开始就 preload回滚会很麻烦。"),
],
"likes": ["ops_alan", "linux_mason", "sec_neo", "dev_yuki"],
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie"],
},
{
"title": "OpenClaw 在 Ubuntu 服务器安装与 systemd 守护(可维护版)",
"author": "dev_yuki",
"category": "运维经验",
"days_ago": 4,
"content": """分享我在 Ubuntu 22.04 上部署 OpenClaw 的方式,重点是“稳定运行 + 易于维护”。
推荐目录结构:
- `/opt/openclaw`:程序目录
- `/etc/openclaw/`:配置目录
- `/var/log/openclaw/`:日志目录
部署建议:
1. 用独立系统用户运行(不要用 root
2. 配置文件与程序文件分离,便于升级回滚。
3. 写 systemd 服务:
- `Restart=always`
- `RestartSec=5`
- 限制权限(`NoNewPrivileges=true` 等)
4. 日志统一走 journald 或文件滚动,避免磁盘被打满。
5. 升级流程采用“解压新版本 -> 切换软链 -> 重启服务”。
上线前 checklist
- `systemctl status openclaw`
- `journalctl -u openclaw -n 200`
- 健康检查接口可用
- 端口仅对需要的来源开放
这样做的好处是:故障定位快、升级可回滚、不会因为单次异常导致服务长期不可用。""",
"comments": [
("ops_alan", "目录分层 + 软链切换这个方案很稳,适合长期维护。"),
("cloud_nana", "建议再加个健康检查脚本,配合告警一起用效果更好。"),
],
"likes": ["ops_alan", "cloud_nana", "linux_mason"],
"bookmarks": ["ops_alan", "sec_neo", "ai_rookie"],
},
{
"title": "宝塔部署 FlaskGunicorn + Nginx + Supervisor 一次跑通",
"author": "linux_mason",
"category": "运维经验",
"days_ago": 3,
"content": """很多同学卡在“本地能跑,宝塔上线 502”。我这边给一个稳定组合
架构:
Nginx (80/443) -> Gunicorn (127.0.0.1:8000) -> Flask App
关键点:
1. Flask 不直接对外暴露Gunicorn 只监听本机回环地址。
2. Nginx `proxy_pass` 指向 Gunicorn注意 `proxy_set_header` 要完整。
3. Gunicorn worker 数量按 CPU 计算,不要盲目拉满。
4. 用 Supervisor/systemd 托管 Gunicorn防止进程意外退出。
5. 目录权限统一,避免静态文件 403。
常见坑:
- 虚拟环境没激活导致依赖缺失;
- Nginx 与 Gunicorn socket/端口不一致;
- 项目根目录配置错误导致模块导入失败。
建议每次发布后执行:
- `nginx -t`
- 访问健康检查 URL
- 查看 Nginx 和应用日志是否有 5xx。""",
"comments": [
("dev_yuki", "Gunicorn 只监听 127.0.0.1 这点很重要,安全收益很高。"),
("ai_rookie", "终于理解 502 的定位方式了,之前一直只看应用日志。"),
],
"likes": ["ops_alan", "dev_yuki", "cloud_nana", "ai_rookie"],
"bookmarks": ["cloud_nana", "ai_rookie", "sec_neo"],
},
{
"title": "为什么放行了 80/443 还是打不开网站15 分钟排障流程",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 2,
"is_pinned": True,
"content": """我把安全组 80/443 放开了,但浏览器还是超时。请大家帮忙看看排障顺序是否正确:
我目前做了:
1. 云安全组入站已放行 80/443来源 0.0.0.0/0
2. Ubuntu 上 `ufw allow 80,443/tcp`。
3. Nginx 已启动,`systemctl status nginx` 显示 active。
4. 域名 A 记录已指向服务器公网 IP。
我准备进一步排查:
- `ss -lntp | grep :80` 确认监听地址;
- 本机 curl 127.0.0.1
- 外网 curl 公网 IP
- 检查 Nginx default server 是否误拦截。
如果还有常见遗漏项,请大家补充一下,我整理后回帖反馈结果。""",
"comments": [
("sec_neo", "再查一下运营商端口封禁和实例是否有公网带宽,这两个也常见。"),
("cloud_nana", "域名解析生效可以用 dig/nslookup 验证,避免本地 DNS 缓存干扰。"),
("ops_alan", "可以先不用域名,直接公网 IP 打通后再回到域名层。"),
],
"likes": ["ops_alan", "cloud_nana", "dev_yuki", "linux_mason"],
"bookmarks": ["ops_alan", "dev_yuki"],
},
{
"title": "新服务器首日初始化 SOP账号、时间、日志、监控一步到位",
"author": "ops_alan",
"category": "运维经验",
"days_ago": 1,
"content": """为了避免“上线后再补安全”这种被动局面,我把新机初始化收敛成一个 SOP
基础项:
- 创建普通运维账号 + sudo
- 配置 SSH 密钥登录;
- 关闭 root 密码登录;
- 设置时区、NTP 同步。
系统项:
- 安装常用诊断工具curl, wget, vim, htop, lsof, ss
- 配置日志轮转;
- 开启防火墙并最小放行。
可观测项:
- 主机监控CPU、内存、磁盘、负载
- 进程可用性检查;
- 磁盘与证书过期告警。
交付项:
- 资产信息登记IP、用途、负责人、到期时间
- 变更记录模板;
- 回滚方案。
这个流程固定下来后,服务器上线质量会稳定很多。""",
"comments": [
("linux_mason", "强烈建议把资产台账自动化,不然机器一多很容易混乱。"),
("sec_neo", "同意,首日就把监控和告警接好,能省很多夜间故障时间。"),
],
"likes": ["dev_yuki", "cloud_nana", "linux_mason", "sec_neo", "ai_rookie"],
"bookmarks": ["dev_yuki", "cloud_nana", "ai_rookie"],
},
{
"title": "服务器端口规划建议Web、数据库、SSH 与内网隔离实践",
"author": "sec_neo",
"category": "综合讨论",
"days_ago": 1,
"is_featured": True,
"content": """分享一个适合中小团队的端口规划思路,核心目标是“暴露最小化”:
公网可见:
- 80/443Web 流量入口(建议统一走 Nginx/网关)
- 22仅白名单 IP最好配合堡垒机
仅内网可见:
- 3306/5432数据库
- 6379缓存
- 9200搜索服务若有
管理面:
- 面板、监控、日志平台尽量不直接暴露公网;
- 必要时使用 VPN / 跳板机访问。
执行原则:
1. 先拒绝后放行default deny
2. 安全组与主机防火墙同时配置。
3. 定期做“端口盘点”和“僵尸规则清理”。
端口规划不是一次性工作,建议每月复盘一次,防止规则持续膨胀。""",
"comments": [
("ops_alan", "我们团队也是按这个思路做,尤其是数据库绝不出公网。"),
("dev_yuki", "建议再加一条:高危端口变更必须走审批和审计。"),
],
"likes": ["ops_alan", "cloud_nana", "dev_yuki", "linux_mason"],
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie", "cloud_nana"],
},
{
"title": "宝塔里 Nginx 显示运行中,但网站一直 502应该按什么顺序排查",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"is_pinned": True,
"content": """我在宝塔上部署了 FlaskNginx 状态是绿色“运行中”,但是访问域名一直 502。
目前我确认过:
1. 域名解析已经指向服务器 IP
2. 80/443 在安全组里放行;
3. 应用进程偶尔会起来,但不稳定。
请问排查顺序是不是这样更高效?
- 看 Nginx 错误日志(确认是 upstream 超时还是连接拒绝);
- 用 `ss -lntp` 看 Gunicorn 是否在监听;
- 本机 curl `127.0.0.1:应用端口`
- 检查宝塔反向代理的目标端口是否写错;
- 查看应用日志是否有导入错误或环境变量缺失。
如果还有常见坑,请大家补充一下,我整理成 checklist。""",
"comments": [
("linux_mason", "顺序正确。先从 Nginx error.log 判断是 connect refused 还是 timeout再决定下一步。"),
("dev_yuki", "补充:检查 Python 虚拟环境路径,宝塔里最容易因为路径错导致 Gunicorn 启动失败。"),
("ops_alan", "再确认一下 systemd/supervisor 是否开启自动拉起,不然进程崩了就一直 502。"),
],
"likes": ["ops_alan", "linux_mason", "dev_yuki", "cloud_nana"],
"bookmarks": ["ops_alan", "dev_yuki", "sec_neo"],
},
{
"title": "域名解析已经改了为什么访问还是旧服务器DNS 缓存怎么判断?",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """我把 A 记录改到新 VPS 后,自己电脑访问还是旧站点,但手机 4G 打开有时是新站。
我怀疑是 DNS 缓存问题,想确认排查步骤:
1. `dig 域名 +short` 看当前解析结果;
2. 对比不同 DNS8.8.8.8 / 1.1.1.1 / 本地运营商);
3. 清本地 DNS 缓存 + 浏览器缓存;
4. 检查是否还有 AAAA 记录指向旧机器;
5. 观察 TTL 到期时间。
有没有更稳妥的切换方案,避免业务迁移时出现“部分用户命中旧站”?""",
"comments": [
("cloud_nana", "迁移前建议先把 TTL 从 600 调到 60等全网生效后再切 IP。"),
("sec_neo", "你提到 AAAA 很关键,很多人只改 A 记录IPv6 用户会继续走旧站。"),
],
"likes": ["cloud_nana", "sec_neo", "ops_alan"],
"bookmarks": ["ops_alan", "linux_mason", "dev_yuki"],
},
{
"title": "Ubuntu 放行了 8080 还是连不上,安全组和防火墙到底谁优先?",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """我在服务器里执行了 `ufw allow 8080/tcp`,应用也监听 `0.0.0.0:8080`,但外网还是连不上。
我想搞清楚优先关系:
- 云安全组没放行时,系统防火墙放行有没有意义?
- 安全组放行了但 UFW 拒绝,会是什么表现?
- 能否用一个最简流程快速判断问题在哪一层?
现在新人常被“服务正常、端口不通”卡住,求一个固定排障模板。""",
"comments": [
("sec_neo", "先看安全组再看系统防火墙,任意一层拒绝都不通。两层都要放行才行。"),
("ops_alan", "建议固定三步:安全组 -> 防火墙 -> 服务监听,避免来回猜。"),
],
"likes": ["sec_neo", "ops_alan", "dev_yuki"],
"bookmarks": ["ai_rookie", "cloud_nana", "linux_mason"],
},
{
"title": "绑定域名后总跳到 Nginx Welcome Pageserver_name 应该怎么配?",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """我已经把域名解析到了服务器,也有自己的站点配置,但访问域名总是落到默认欢迎页。
我检查到:
1. `/etc/nginx/sites-enabled` 里同时存在 default 和我的站点;
2. 我的 server_name 只写了主域名,没写 www
3. 有多个配置都监听 80。
请问正确做法是不是:
- 删除/禁用默认站点;
- server_name 同时写 `example.com` 和 `www.example.com`
- 用 `nginx -t` 检查冲突,再 reload。""",
"comments": [
("linux_mason", "是的,默认站点优先命中非常常见。建议明确一个 default_server其他按域名精确匹配。"),
("cloud_nana", "别忘了 HTTPS 的 server block 也要同步配置,不然 443 还是会走错。"),
],
"likes": ["linux_mason", "cloud_nana", "ops_alan"],
"bookmarks": ["dev_yuki", "ops_alan", "ai_rookie"],
},
{
"title": "OpenClaw 服务启动成功但页面空白,日志里没有明显报错怎么办?",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """我按教程装了 OpenClawsystemd 状态是 active但前端页面空白或一直 loading。
我目前想到的排查:
1. 检查配置文件里 API 地址是否写错(内网/公网);
2. 浏览器开发者工具看网络请求是否 4xx/5xx
3. 检查反向代理路径和 WebSocket 升级头;
4. 看 openclaw 日志级别是否太低,临时改为 debug
5. 校验数据库连接和初始化状态。
有没有人踩过类似坑,最后是哪里的问题?""",
"comments": [
("dev_yuki", "优先看浏览器 network 面板,空白页大概率是静态资源 404 或 API 跨域。"),
("sec_neo", "如果走反代,确认 `Upgrade`/`Connection` 头WebSocket 缺这个会卡 loading。"),
],
"likes": ["dev_yuki", "sec_neo", "cloud_nana"],
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie"],
},
{
"title": "申请 Let's Encrypt 一直失败too many failed authorizations还能怎么处理",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """我在同一个域名上连续尝试了很多次证书申请,现在提示 `too many failed authorizations`。
我理解是被限流了,但不确定下一步:
1. 是不是要先等一段时间再重试?
2. 失败期间怎么保证业务可访问(临时 HTTP / 自签证书)?
3. 下次重试前要先验证哪些条件,避免再次失败?
我现在的域名解析已经正常80 端口也放开了。""",
"comments": [
("cloud_nana", "先等限流窗口过去,再用 `--dry-run` 或 staging 环境验证流程,别直接打生产接口。"),
("ops_alan", "重试前先用 HTTP 明确能访问 `/.well-known/acme-challenge/`,这一步最关键。"),
],
"likes": ["cloud_nana", "ops_alan", "linux_mason"],
"bookmarks": ["dev_yuki", "ops_alan", "ai_rookie"],
},
{
"title": "宝塔用几天后磁盘爆满,新手该先清哪些目录?",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """2C4G 小机器,刚装宝塔一周磁盘就 90% 了,怕直接删错文件。
目前怀疑的占用来源:
- Nginx / 应用日志;
- 宝塔自动备份;
- Docker 镜像与容器日志;
- 数据库 binlog。
有没有一个“安全清理顺序”?
我希望先释放空间,后面再补定期清理策略。""",
"comments": [
("ops_alan", "先 `du -sh /*` 定位大头,再按日志->备份->镜像顺序清理,不要盲删系统目录。"),
("linux_mason", "Docker 场景补一句:`docker system df` 先看占用,再按策略 prune。"),
],
"likes": ["ops_alan", "linux_mason", "sec_neo"],
"bookmarks": ["cloud_nana", "dev_yuki", "ai_rookie"],
},
{
"title": "新手配置选型2核4G 能不能同时跑 Flask + MySQL + OpenClaw",
"author": "ai_rookie",
"category": "新手提问",
"days_ago": 0,
"content": """预算有限,先买了 2核4G 机器,想跑:
1. Flask 网站
2. MySQL
3. OpenClaw
担心点:
- 高峰时内存不够导致 OOM
- MySQL 占用太大拖慢接口;
- 后续扩容迁移复杂。
有没有比较稳妥的起步建议?例如哪些服务先拆、哪些参数先限制?""",
"comments": [
("dev_yuki", "2核4G 能跑,但建议先把 MySQL 参数收紧Gunicorn worker 不要开太多。"),
("cloud_nana", "如果流量上来,优先把数据库拆到独立实例,应用层水平扩展更简单。"),
("sec_neo", "别忘了开启 swap 兜底,但它只是缓冲,不能替代真正扩容。"),
],
"likes": ["dev_yuki", "cloud_nana", "sec_neo", "ops_alan"],
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie", "cloud_nana"],
},
{
"title": "OpenClaw 安装实战:从 0 到可用(官方安装页版)",
"author": "dev_yuki",
"category": "运维经验",
"days_ago": 0,
"is_featured": True,
"content": """这篇按官方安装页https://openclaw.im/#install整理目标是新手 10-20 分钟跑通。
一、安装方式(推荐一键脚本)
1. 服务器执行:
`curl -fsSL https://openclaw.im/install.sh | bash`
2. 安装后检查:
`openclaw --version`
如果你更习惯包管理器,也可以:
- npm: `npm install -g openclaw@latest`
- pnpm: `pnpm add -g openclaw@latest`
二、初始化(关键步骤)
1. 执行引导:
`openclaw onboard --install-daemon`
2. 按提示完成 Provider、模型、存储等配置。
3. 安装完成后,建议先跑健康检查:
`openclaw gateway status`
三、Web 控制台与连接
1. 默认控制台地址:
`http://127.0.0.1:18789/`
2. 如果你是远程服务器,建议用 Nginx 反向代理并开启 HTTPS。
3. 需要连接频道时,使用:
`openclaw channels login`
四、Docker 部署(可选)
官方也提供 Docker 工作流,常见顺序:
1. `./docker-setup.sh`
2. `docker compose run --rm openclaw-cli onboard`
3. `docker compose up -d openclaw-gateway`
五、排错清单(最常见)
1. 命令执行失败:先检查 Node 版本(官方建议 Node >= 22
2. 页面打不开:确认 18789 端口监听与防火墙/安全组放行。
3. 网关状态异常:先看 `openclaw gateway status`,再复查 onboard 配置。
4. 远程访问不稳定:优先通过反向代理统一入口,不直接暴露高风险端口。
六、上线建议
1. 把配置与日志目录分离,方便升级和回滚。
2. 使用守护方式运行onboard 的 daemon 选项),避免进程意外退出。
3. 做最小暴露:仅开放必要端口,后台入口加访问控制。""",
"comments": [
("ops_alan", "这篇很适合新手,先一键安装再做反代是比较稳的路径。"),
("sec_neo", "建议补一句:公网部署务必加 HTTPS 和访问控制,别裸奔。"),
("ai_rookie", "我刚按这个流程跑通了,`openclaw gateway status` 这个检查很有用。"),
],
"likes": ["ops_alan", "cloud_nana", "linux_mason", "sec_neo", "ai_rookie"],
"bookmarks": ["ops_alan", "cloud_nana", "ai_rookie"],
},
]
def _utcnow_naive():
"""兼容 Python 3.13:返回无时区 UTC 时间。"""
return datetime.now(timezone.utc).replace(tzinfo=None)
def _resolve_category(name):
if not name:
return "综合讨论"
row = ForumCategory.query.filter_by(name=name).first()
if row:
return row.name
active = ForumCategory.query.filter_by(is_active=True).order_by(ForumCategory.sort_order.asc()).first()
if active:
return active.name
any_row = ForumCategory.query.order_by(ForumCategory.sort_order.asc()).first()
if any_row:
return any_row.name
return "综合讨论"
def _get_or_create_user(username, password):
user = User.query.filter_by(username=username).first()
created = False
if user is None:
user = User(username=username)
user.set_password(password)
user.last_login_at = _utcnow_naive()
db.session.add(user)
db.session.flush()
created = True
return user, created
def main():
created_users = 0
skipped_users = 0
created_posts = 0
skipped_posts = 0
created_comments = 0
created_likes = 0
created_bookmarks = 0
with app.app_context():
user_map = {}
for u in USERS:
user, created = _get_or_create_user(u["username"], u["password"])
user_map[u["username"]] = user
if created:
created_users += 1
else:
skipped_users += 1
db.session.flush()
for idx, spec in enumerate(POSTS):
title = spec["title"].strip()
author = user_map.get(spec["author"])
if not author:
continue
exists = ForumPost.query.filter_by(title=title).first()
if exists:
skipped_posts += 1
continue
created_at = _utcnow_naive() - timedelta(days=int(spec.get("days_ago", 0)), hours=max(0, idx))
post = ForumPost(
user_id=author.id,
category=_resolve_category(spec.get("category")),
title=title,
content=spec["content"].strip(),
is_pinned=bool(spec.get("is_pinned")),
is_featured=bool(spec.get("is_featured")),
is_locked=bool(spec.get("is_locked")),
view_count=120 + idx * 19,
created_at=created_at,
updated_at=created_at,
)
db.session.add(post)
db.session.flush()
created_posts += 1
for c_idx, (comment_user, comment_text) in enumerate(spec.get("comments", [])):
c_user = user_map.get(comment_user)
if not c_user:
continue
comment_at = created_at + timedelta(hours=2 + c_idx * 3)
db.session.add(ForumComment(
post_id=post.id,
user_id=c_user.id,
content=comment_text.strip(),
created_at=comment_at,
updated_at=comment_at,
))
created_comments += 1
for like_user in spec.get("likes", []):
l_user = user_map.get(like_user)
if not l_user:
continue
db.session.add(ForumPostLike(post_id=post.id, user_id=l_user.id))
created_likes += 1
for bookmark_user in spec.get("bookmarks", []):
b_user = user_map.get(bookmark_user)
if not b_user:
continue
db.session.add(ForumPostBookmark(post_id=post.id, user_id=b_user.id))
created_bookmarks += 1
db.session.commit()
print("用户:新增 {},已存在 {}".format(created_users, skipped_users))
print("帖子:新增 {},已存在 {}".format(created_posts, skipped_posts))
print("评论:新增 {}".format(created_comments))
print("点赞:新增 {}".format(created_likes))
print("收藏:新增 {}".format(created_bookmarks))
print("默认测试密码(新用户):{}".format(DEFAULT_PASSWORD))
if __name__ == "__main__":
main()