通过自动提及和标签创建功能,增强文章编辑功能

- 在文章编辑过程中,实现了自动创建不存在的@提及和#标签的功能,确保它们被添加到相应的数据库中。
- 更新了内容处理逻辑,以利用新创建的提及和标签,从而改善用户体验和内容管理。
- 增强了人物和链接标签创建的后端处理能力,使文章编辑过程中能够实现无缝集成。
This commit is contained in:
Alex-larget
2026-03-16 11:09:26 +08:00
parent b3ce6b5445
commit d4ba905ee5
29 changed files with 732 additions and 10 deletions

View File

@@ -0,0 +1,18 @@
# 测试环境配置示例。复制为 .env.test 后按需修改。
# 运行 pytest 前必须明确指定测试环境,避免误测正式库。
# 测试环境(必填其一)
# local = 本地 http://localhost:8080
# souldev = 测试 https://souldev.quwanzhi.com
# soulapi = 正式 https://soulapi.quwanzhi.com慎用
SOUL_TEST_ENV=local
# 或直接指定 API 地址(覆盖 SOUL_TEST_ENV
# SOUL_API_BASE=http://localhost:8080
# 管理端登录(默认 admin/admin123
# SOUL_ADMIN_USERNAME=admin
# SOUL_ADMIN_PASSWORD=admin123
# 小程序开发登录 userId仅 APP_ENV=development 时可用)
# SOUL_MINIPROGRAM_DEV_USER_ID=ogpTW5fmXRGNpoUbXB3UEqnVe5Tg

4
scripts/test/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.env.test
__pycache__/
.pytest_cache/
*.pyc

63
scripts/test/README.md Normal file
View File

@@ -0,0 +1,63 @@
# Soul 创业派对 - 测试用例目录
> 测试工程师在此编写与维护测试用例。使用 pytest + requests 架构。
---
## 目录结构
| 子目录 | 用途 | 对应端 | API 路径 |
|--------|------|--------|----------|
| **miniapp/** | 小程序接口测试 | miniprogram | /api/miniprogram/* |
| **web/** | 管理端测试 | soul-admin | /api/admin/*、/api/db/* |
| **process/** | 流程测试 | 跨端 | 多接口串联 |
---
## 快速开始
```bash
cd scripts/test
pip install -r requirements-test.txt
pytest -v
```
联调前请先执行 `scripts/本地启动.sh` 启动 soul-api 与 soul-admin。
---
## 环境变量(必须明确指定测试环境)
| 变量 | 说明 | 示例 |
|------|------|------|
| **SOUL_TEST_ENV** | 测试环境 | local / souldev / soulapi |
| **SOUL_API_BASE** | 或直接指定 API 地址 | http://localhost:8080 |
| SOUL_ADMIN_USERNAME | 管理端账号 | admin |
| SOUL_ADMIN_PASSWORD | 管理端密码 | admin123 |
| SOUL_MINIPROGRAM_DEV_USER_ID | 小程序开发登录 userId | 空(需 APP_ENV=development |
可复制 `.env.test.example``.env.test` 配置(`.env.test` 含账号等,勿提交)。
**运行前会在报告头部显示「测试环境: xxx」**,避免误测正式库。
---
## 运行方式
```bash
pytest miniapp/ -v # 只跑小程序
pytest web/ -v # 只跑管理端
pytest process/ -v # 只跑流程
pytest -v # 全量
```
---
## 文件说明
| 文件 | 说明 |
|------|------|
| config.py | 配置API_BASE、登录账号等 |
| conftest.py | 共享 fixturesbase_url、admin_token、miniapp_token |
| util.py | 工具函数admin_headers、miniapp_headers |
| requirements-test.txt | pytest、requests |

119
scripts/test/config.py Normal file
View File

@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""
测试配置。从项目 soul-api/.env* 或 scripts/test/.env.test 或环境变量读取。
必须明确指定测试环境,避免误测正式库。
"""
import os
from pathlib import Path
# 项目根目录scripts/test 的上级的上级)
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
SOUL_API_ENV = PROJECT_ROOT / "soul-api"
TEST_DIR = Path(__file__).resolve().parent
def _apply_env_file(path: Path) -> None:
"""将 .env 文件中的变量加载到 os.environ仅当未设置时"""
if not path.exists():
return
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
k = k.strip()
v = v.strip().strip('"').strip("'")
if k and k not in os.environ:
os.environ[k] = v
# 优先加载 scripts/test/.env.test本地覆盖
_apply_env_file(TEST_DIR / ".env.test")
# 环境与 API 地址映射(与 miniprogram/app.js、soul-api/.env* 一致)
ENV_PROFILES = {
"local": "http://localhost:8080",
"souldev": "https://souldev.quwanzhi.com",
"soulapi": "https://soulapi.quwanzhi.com",
}
# 环境中文名(用于提示)
ENV_LABELS = {
"local": "本地",
"souldev": "测试",
"soulapi": "正式",
}
def _load_env_file(path: Path) -> dict:
"""解析 .env 文件为 dict"""
out = {}
if not path.exists():
return out
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
out[k.strip()] = v.strip().strip('"').strip("'")
return out
def _resolve_api_base() -> tuple[str, str]:
"""
解析 API 地址与当前环境标签。
优先级SOUL_TEST_ENV > SOUL_API_BASE > 从 soul-api/.env 读取 > 默认 local
"""
env_val = os.environ.get("SOUL_TEST_ENV", "").strip().lower()
explicit_base = os.environ.get("SOUL_API_BASE", "").strip().rstrip("/")
if explicit_base:
# 显式指定了地址,根据地址推断环境标签
label = "自定义"
for k, v in ENV_PROFILES.items():
if v.rstrip("/") == explicit_base:
label = ENV_LABELS.get(k, k)
break
return explicit_base, label
if env_val in ENV_PROFILES:
return ENV_PROFILES[env_val], ENV_LABELS.get(env_val, env_val)
# 尝试从 soul-api/.env 读取 API_BASE_URL
env_path = SOUL_API_ENV / ".env"
env_dev = SOUL_API_ENV / ".env.development"
env_prod = SOUL_API_ENV / ".env.production"
for p in [env_path, env_dev, env_prod]:
loaded = _load_env_file(p)
if loaded.get("API_BASE_URL"):
base = loaded["API_BASE_URL"].rstrip("/")
for k, v in ENV_PROFILES.items():
if v.rstrip("/") == base:
return base, ENV_LABELS.get(k, k)
return base, "项目配置"
# 默认本地,并提示未显式指定
return ENV_PROFILES["local"], ENV_LABELS["local"]
API_BASE, ENV_LABEL = _resolve_api_base()
# 管理端登录(与 scripts/本地启动.sh 一致;不同环境账号可能不同)
ADMIN_USERNAME = os.environ.get("SOUL_ADMIN_USERNAME", "admin")
ADMIN_PASSWORD = os.environ.get("SOUL_ADMIN_PASSWORD", "admin123")
# 小程序开发环境登录用 userId仅 local/souldev 且 APP_ENV=development 时可用)
MINIAPP_DEV_USER_ID = os.environ.get("SOUL_MINIPROGRAM_DEV_USER_ID", "")
def get_env_banner() -> str:
"""返回测试环境提示横幅"""
return (
"\n"
"========================================\n"
f" 测试环境: {ENV_LABEL} ({API_BASE})\n"
"========================================\n"
" 若需切换,请设置: SOUL_TEST_ENV=local|souldev|soulapi\n"
" 或: SOUL_API_BASE=<完整 API 地址>\n"
"========================================\n"
)

67
scripts/test/conftest.py Normal file
View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
"""
共享 fixturesbase_url、admin_token、miniapp_token
"""
import pytest
import requests
from config import (
API_BASE,
ADMIN_USERNAME,
ADMIN_PASSWORD,
MINIAPP_DEV_USER_ID,
get_env_banner,
)
def pytest_report_header(config):
"""pytest 报告头部显示测试环境,避免误测"""
return get_env_banner().strip().split("\n")
@pytest.fixture(scope="session")
def base_url():
"""API 基础地址"""
return API_BASE
@pytest.fixture(scope="session")
def admin_token(base_url):
"""
管理端 JWT。通过 POST /api/admin 登录获取。
失败时返回空字符串,用例可 skip。
"""
try:
r = requests.post(
f"{base_url}/api/admin",
json={"username": ADMIN_USERNAME, "password": ADMIN_PASSWORD},
timeout=10,
)
data = r.json()
if data.get("success") and data.get("token"):
return data["token"]
except Exception:
pass
return ""
@pytest.fixture(scope="session")
def miniapp_token(base_url):
"""
小程序 token。通过 POST /api/miniprogram/dev/login-as 获取(仅 APP_ENV=development
若 MINIAPP_DEV_USER_ID 未配置或接口不可用,返回空字符串。
"""
if not MINIAPP_DEV_USER_ID:
return ""
try:
r = requests.post(
f"{base_url}/api/miniprogram/dev/login-as",
json={"userId": MINIAPP_DEV_USER_ID},
timeout=10,
)
data = r.json()
if data.get("success") and data.get("data", {}).get("token"):
return data["data"]["token"]
except Exception:
pass
return ""

View File

@@ -0,0 +1,19 @@
# 小程序接口测试 (miniapp)
> 小程序 C 端接口测试用例。对应 miniprogramAPI 路径:`/api/miniprogram/*`
---
## 测试范围
- 登录微信登录、手机号、token 持久化)
- 购买与支付(下单、微信支付、回调、购买状态)
- 推荐与分润(扫码/分享带 ref、绑定、分润计算
- VIP 功能(开通、资料、头像上传、排行展示)
- 阅读(文章列表、详情、预览、全文)
---
## 用例编写
在此目录下新增 `.md` 或测试脚本,按场景组织用例。

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""miniapp 专用 fixtures继承 scripts/test/conftest.py"""

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""
小程序公开接口测试。GET /api/miniprogram/config 无需鉴权。
"""
import pytest
import requests
def test_config_public(base_url):
"""GET /api/miniprogram/config 返回配置"""
r = requests.get(f"{base_url}/api/miniprogram/config", timeout=10)
assert r.status_code == 200
data = r.json()
assert data.get("success") is True
assert "prices" in data
assert "features" in data
assert "mpConfig" in data or "mp_config" in data

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
小程序开发环境登录。POST /api/miniprogram/dev/login-as仅 APP_ENV=development
需配置 SOUL_MINIPROGRAM_DEV_USER_ID 环境变量。
"""
import pytest
import requests
from config import MINIAPP_DEV_USER_ID
@pytest.mark.skipif(
not MINIAPP_DEV_USER_ID,
reason="SOUL_MINIPROGRAM_DEV_USER_ID 未配置,跳过开发登录测试",
)
def test_dev_login_as(base_url):
"""开发环境按 userId 登录"""
r = requests.post(
f"{base_url}/api/miniprogram/dev/login-as",
json={"userId": MINIAPP_DEV_USER_ID},
timeout=10,
)
assert r.status_code == 200
data = r.json()
assert data.get("success") is True
assert "data" in data
assert "token" in data["data"]
assert "user" in data["data"]

View File

@@ -0,0 +1,19 @@
# 流程测试 (process)
> 跨端业务流程测试用例。验证多步骤、多接口串联的完整流程。
---
## 测试范围
- **下单→支付→回调→分润**:购买全链路
- **推荐码绑定→访问记录→分润计算**:推广流程
- **VIP 开通→资料填写→排行展示**:会员流程
- **提现申请→审核→到账**:提现流程
- **内容发布→审核→上架→用户可见**:内容流转
---
## 用例编写
在此目录下新增 `.md` 或测试脚本,按业务流程组织用例。流程测试通常涉及 miniprogram + admin + API 多端联动。

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""process 专用 fixtures继承 scripts/test/conftest.py"""

View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""
流程测试前置:健康检查。确保 soul-api 已启动。
"""
import requests
def test_health(base_url):
"""GET /health 健康检查"""
r = requests.get(f"{base_url}/health", timeout=5)
assert r.status_code == 200

4
scripts/test/pytest.ini Normal file
View File

@@ -0,0 +1,4 @@
[pytest]
pythonpath = .
testpaths = miniapp web process
addopts = -v

View File

@@ -0,0 +1,2 @@
pytest>=7.0
requests>=2.28

12
scripts/test/util.py Normal file
View File

@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
"""测试工具函数"""
def admin_headers(token):
"""管理端请求头"""
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
def miniapp_headers(token):
"""小程序请求头"""
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}

View File

@@ -0,0 +1,19 @@
# 管理端测试 (web)
> 管理后台功能测试用例。对应 soul-adminAPI 路径:`/api/admin/*`、`/api/db/*`
---
## 测试范围
- 内容管理(文章、章节、书籍 CRUD
- 用户管理
- 订单、提现
- VIP 角色、推广设置
- 导师、导师预约、二维码、站点、支付配置
---
## 用例编写
在此目录下新增 `.md` 或测试脚本,按场景组织用例。

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""web 专用 fixtures继承 scripts/test/conftest.py"""

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""
管理端鉴权测试。POST /api/admin 登录GET /api/admin 鉴权检查。
"""
import pytest
import requests
from util import admin_headers
def test_admin_login(base_url):
"""POST /api/admin 登录成功"""
r = requests.post(
f"{base_url}/api/admin",
json={"username": "admin", "password": "admin123"},
timeout=10,
)
assert r.status_code == 200
data = r.json()
assert data.get("success") is True
assert "token" in data
assert "user" in data
def test_admin_check_with_token(admin_token, base_url):
"""GET /api/admin 带 token 鉴权通过"""
if not admin_token:
pytest.skip("admin 登录失败,跳过鉴权测试")
r = requests.get(
f"{base_url}/api/admin",
headers=admin_headers(admin_token),
timeout=10,
)
assert r.status_code == 200
data = r.json()
assert data.get("success") is True
def test_admin_check_without_token(base_url):
"""GET /api/admin 无 token 返回 401"""
r = requests.get(f"{base_url}/api/admin", timeout=10)
assert r.status_code == 401