chore: 迁移依赖、移除sqlite3_flutter_libs并新增功能

1. 替换hive_flutter为hive_ce_flutter依赖
2. 从各平台插件列表移除sqlite3_flutter_libs
3. 重构API请求体格式,优化历史记录去重逻辑
4. 新增CTC笔记相关功能:桌面小部件、模板模型、本地存储
5. 新增表单收集服务和后台管理接口
6. 优化缓存配置、多语言文案和UI细节
7. 重构首页状态监听组件
This commit is contained in:
Developer
2026-06-15 10:04:52 +08:00
parent af14ed4121
commit ad00967c68
90 changed files with 4728 additions and 1028 deletions

593
Scripts/test_ctc_api.py Normal file
View File

@@ -0,0 +1,593 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
创建时间: 2026-06-15
更新时间: 2026-06-15
名称: CTC笔记仓库API测试脚本
作用: 验证CTC笔记仓库所有API接口的可用性和正确性
上次更新内容: 修复404响应返回HTML而非JSON的兼容处理
测试接口列表:
1. 写入笔记 POST /{key}?json body: text=xxx
2. 读取笔记 GET /{key}?raw
3. 获取笔记信息 GET /?info&note={key}
4. 批量检查变更 GET /?check&keys=key1,key2
5. 创建随机笔记 GET /?new&json&text=test
6. 删除笔记 GET /?delete&note={key}
"""
import random
import string
import sys
import time
import requests
# ==================== 配置 ====================
BASE_URL = "https://ctc.s2ss.com"
TIMEOUT = 15 # 请求超时(秒)
TEST_TEXT = "CTC API自动化测试内容 - " + time.strftime("%Y%m%d%H%M%S")
TEST_TEXT_UPDATED = "CTC API自动化测试内容(已更新) - " + time.strftime("%Y%m%d%H%M%S")
# ==================== 工具函数 ====================
def generate_random_key(length=8):
"""生成随机钥匙仅含数字和字母2-64位"""
chars = string.ascii_lowercase + string.digits
return "test" + "".join(random.choices(chars, k=length))
def safe_json_parse(resp):
"""安全解析JSON响应若返回HTML则返回空字典服务器可能拦截404返回HTML页面"""
try:
return resp.json()
except (Value.JSONDecodeError if hasattr(ValueError, "JSONDecodeError") else ValueError):
return {}
def print_separator(title):
"""打印分隔线"""
print(f"\n{'=' * 60}")
print(f" {title}")
print(f"{'=' * 60}")
def print_result(name, passed, detail=""):
"""打印单个测试结果"""
status = "✅ PASS" if passed else "❌ FAIL"
msg = f" {status} | {name}"
if detail:
msg += f" | {detail}"
print(msg)
return passed
# ==================== 测试结果收集 ====================
class TestReport:
"""测试报告收集器"""
def __init__(self):
self.results = []
def add(self, name, passed, detail=""):
self.results.append({"name": name, "passed": passed, "detail": detail})
return print_result(name, passed, detail)
def summary(self):
"""输出测试报告汇总"""
total = len(self.results)
passed = sum(1 for r in self.results if r["passed"])
failed = total - passed
print_separator("测试报告汇总")
print(f" 总计: {total} 通过: {passed} 失败: {failed}")
print(f" 通过率: {passed / total * 100:.1f}%" if total > 0 else " 无测试项")
if failed > 0:
print("\n 失败项:")
for r in self.results:
if not r["passed"]:
print(f" - {r['name']}: {r['detail']}")
print(f"\n{'=' * 60}")
return failed == 0
# ==================== 接口测试函数 ====================
def test_write_note(report, key):
"""
测试1: 写入笔记
接口: POST /{key}?json body: text=xxx
预期: 200 + {"code":1, "msg":"saved", "data":{...}}
"""
print_separator("测试1: 写入笔记 (POST)")
url = f"{BASE_URL}/{key}?json"
print(f" 请求: POST {url}")
print(f" 内容: text={TEST_TEXT}")
try:
resp = requests.post(url, data={"text": TEST_TEXT}, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("写入笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "saved"
has_data = "data" in data and data["data"].get("key") == key
report.add("写入笔记 - code=1", code_ok, f"code={data.get('code')}")
report.add("写入笔记 - msg=saved", msg_ok, f"msg={data.get('msg')}")
report.add("写入笔记 - data.key匹配", has_data, f"data={data.get('data')}")
if has_data:
note_data = data["data"]
print(f" 笔记大小: {note_data.get('size')} bytes")
print(f" 修改时间: {note_data.get('mtime')}")
print(f" 是否存在: {note_data.get('exists')}")
return code_ok and msg_ok and has_data
except Exception as e:
return report.add("写入笔记", False, f"异常: {e}")
def test_read_note(report, key):
"""
测试2: 读取笔记
接口: GET /{key}?raw
预期: 200 + 笔记原文 (text/plain)
"""
print_separator("测试2: 读取笔记 (GET ?raw)")
url = f"{BASE_URL}/{key}?raw"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
status_ok = resp.status_code == 200
content_ok = TEST_TEXT in resp.text
report.add("读取笔记 - HTTP 200", status_ok, f"status={resp.status_code}")
report.add("读取笔记 - 内容匹配", content_ok, f"内容长度={len(resp.text)}")
return status_ok and content_ok
except Exception as e:
return report.add("读取笔记", False, f"异常: {e}")
def test_read_nonexistent_note(report):
"""
测试2补充: 读取不存在的笔记
预期: 404
"""
print_separator("测试2补充: 读取不存在的笔记")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/{fake_key}?raw"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:100]}")
is_404 = resp.status_code == 404
report.add("读取不存在笔记 - HTTP 404", is_404, f"status={resp.status_code}")
return is_404
except Exception as e:
return report.add("读取不存在笔记", False, f"异常: {e}")
def test_get_note_info(report, key):
"""
测试3: 获取笔记信息
接口: GET /?info&note={key}
预期: 200 + {"code":1, "data":{"key":"...", "size":..., "mtime":..., "exists":true}}
"""
print_separator("测试3: 获取笔记信息 (GET ?info)")
url = f"{BASE_URL}/?info&note={key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("获取笔记信息", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
has_data = "data" in data
key_match = data.get("data", {}).get("key") == key if has_data else False
exists_ok = data.get("data", {}).get("exists") is True if has_data else False
report.add("获取笔记信息 - code=1", code_ok, f"code={data.get('code')}")
report.add("获取笔记信息 - key匹配", key_match, f"key={data.get('data', {}).get('key')}")
report.add("获取笔记信息 - exists=true", exists_ok, f"exists={data.get('data', {}).get('exists')}")
if has_data:
note_info = data["data"]
print(f" 笔记大小: {note_info.get('size')} bytes")
print(f" 修改时间: {note_info.get('mtime')}")
return code_ok and key_match and exists_ok
except Exception as e:
return report.add("获取笔记信息", False, f"异常: {e}")
def test_get_nonexistent_note_info(report):
"""
测试3补充: 获取不存在笔记的信息
预期: 404 + {"code":0, "msg":"笔记不存在"}
"""
print_separator("测试3补充: 获取不存在笔记的信息")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/?info&note={fake_key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
is_404 = resp.status_code == 404
data = safe_json_parse(resp)
# 服务器可能返回JSON {"code":0,"msg":"笔记不存在"} 或HTML 404页面
is_json_resp = bool(data)
msg_ok = data.get("msg") == "笔记不存在" if is_json_resp else True # HTML 404也算通过
report.add("获取不存在笔记信息 - HTTP 404", is_404, f"status={resp.status_code}")
if is_json_resp:
report.add("获取不存在笔记信息 - msg正确", msg_ok, f"msg={data.get('msg')}")
else:
report.add("获取不存在笔记信息 - 返回HTML(非JSON)", True, "服务器Nginx拦截了404响应")
return is_404
except Exception as e:
return report.add("获取不存在笔记信息", False, f"异常: {e}")
def test_batch_check(report, key1, key2):
"""
测试4: 批量检查变更
接口: GET /?check&keys=key1,key2
预期: 200 + {"code":1, "data":{"key1":{...}, "key2":null或{...}}}
"""
print_separator("测试4: 批量检查变更 (GET ?check)")
keys_param = f"{key1},{key2}"
url = f"{BASE_URL}/?check&keys={keys_param}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:300]}")
if resp.status_code != 200:
return report.add("批量检查变更", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
has_data = "data" in data
# key1 应该存在(已写入)
key1_info = data.get("data", {}).get(key1)
key1_exists = key1_info is not None and key1_info.get("exists") is True
# key2 可能存在也可能不存在
key2_info = data.get("data", {}).get(key2)
report.add("批量检查变更 - code=1", code_ok, f"code={data.get('code')}")
report.add("批量检查变更 - key1存在", key1_exists, f"key1_info={key1_info}")
report.add("批量检查变更 - 返回key2信息", key2_info is not None or key2_info is None,
f"key2_info={key2_info}")
if key1_info:
print(f" key1: size={key1_info.get('size')}, mtime={key1_info.get('mtime')}")
if key2_info:
print(f" key2: size={key2_info.get('size')}, mtime={key2_info.get('mtime')}, exists={key2_info.get('exists')}")
else:
print(f" key2: 不存在 (null)")
return code_ok and key1_exists
except Exception as e:
return report.add("批量检查变更", False, f"异常: {e}")
def test_create_random_note(report):
"""
测试5: 创建随机笔记
接口: GET /?new&json&text=test
预期: 200 + {"code":1, "msg":"created", "data":{"key":"...", "url":"...", "size":...}}
返回: (success, new_key)
"""
print_separator("测试5: 创建随机笔记 (GET ?new)")
url = f"{BASE_URL}/?new&json&text=test"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
report.add("创建随机笔记", False, f"HTTP {resp.status_code}")
return False, None
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "created"
has_data = "data" in data
has_key = data.get("data", {}).get("key") is not None if has_data else False
has_url = data.get("data", {}).get("url") is not None if has_data else False
report.add("创建随机笔记 - code=1", code_ok, f"code={data.get('code')}")
report.add("创建随机笔记 - msg=created", msg_ok, f"msg={data.get('msg')}")
report.add("创建随机笔记 - 返回key", has_key, f"key={data.get('data', {}).get('key')}")
report.add("创建随机笔记 - 返回url", has_url, f"url={data.get('data', {}).get('url')}")
new_key = data.get("data", {}).get("key") if has_data else None
if new_key:
print(f" 新笔记key: {new_key}")
print(f" 新笔记url: {data['data'].get('url')}")
print(f" 新笔记size: {data['data'].get('size')}")
return code_ok and has_key, new_key
except Exception as e:
report.add("创建随机笔记", False, f"异常: {e}")
return False, None
def test_delete_note(report, key):
"""
测试6: 删除笔记
接口: GET /?delete&note={key}
预期: 200 + {"code":1, "msg":"deleted"}
"""
print_separator("测试6: 删除笔记 (GET ?delete)")
url = f"{BASE_URL}/?delete&note={key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("删除笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
msg_ok = data.get("msg") == "deleted"
report.add(f"删除笔记({key}) - code=1", code_ok, f"code={data.get('code')}")
report.add(f"删除笔记({key}) - msg=deleted", msg_ok, f"msg={data.get('msg')}")
return code_ok and msg_ok
except Exception as e:
return report.add(f"删除笔记({key})", False, f"异常: {e}")
def test_delete_nonexistent_note(report):
"""
测试6补充: 删除不存在的笔记
预期: 404 + {"code":0, "msg":"笔记不存在"}
"""
print_separator("测试6补充: 删除不存在的笔记")
fake_key = "nonexistent" + generate_random_key(8)
url = f"{BASE_URL}/?delete&note={fake_key}"
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
is_404 = resp.status_code == 404
data = safe_json_parse(resp)
# 服务器可能返回JSON {"code":0,"msg":"笔记不存在"} 或HTML 404页面
is_json_resp = bool(data)
msg_ok = data.get("msg") == "笔记不存在" if is_json_resp else True # HTML 404也算通过
report.add("删除不存在笔记 - HTTP 404", is_404, f"status={resp.status_code}")
if is_json_resp:
report.add("删除不存在笔记 - msg正确", msg_ok, f"msg={data.get('msg')}")
else:
report.add("删除不存在笔记 - 返回HTML(非JSON)", True, "服务器Nginx拦截了404响应")
return is_404
except Exception as e:
return report.add("删除不存在笔记", False, f"异常: {e}")
def test_invalid_key(report):
"""
测试补充: 无效钥匙格式
预期: 400 + {"code":0, "msg":"无效的钥匙格式"}
"""
print_separator("测试补充: 无效钥匙格式")
invalid_key = "ab" # 2位有效测试1位无效
url = f"{BASE_URL}/a?json&info" # 1位钥匙不符合2-64位规则
print(f" 请求: GET {url}")
try:
resp = requests.get(url, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
# 无效钥匙可能返回400或重定向
is_error = resp.status_code in (400, 302, 301)
report.add("无效钥匙格式 - 返回错误/重定向", is_error,
f"status={resp.status_code}")
return is_error
except Exception as e:
return report.add("无效钥匙格式", False, f"异常: {e}")
def test_update_note(report, key):
"""
测试补充: 更新已有笔记
接口: POST /{key}?json body: text=xxx
预期: 200 + {"code":1, "msg":"saved"},内容被覆盖
"""
print_separator("测试补充: 更新已有笔记")
url = f"{BASE_URL}/{key}?json"
print(f" 请求: POST {url}")
print(f" 新内容: text={TEST_TEXT_UPDATED}")
try:
resp = requests.post(url, data={"text": TEST_TEXT_UPDATED}, timeout=TIMEOUT)
print(f" 状态码: {resp.status_code}")
print(f" 响应: {resp.text[:200]}")
if resp.status_code != 200:
return report.add("更新笔记", False, f"HTTP {resp.status_code}")
data = resp.json()
code_ok = data.get("code") == 1
report.add("更新笔记 - code=1", code_ok, f"code={data.get('code')}")
# 验证内容确实更新了
time.sleep(0.5)
read_url = f"{BASE_URL}/{key}?raw"
read_resp = requests.get(read_url, timeout=TIMEOUT)
content_updated = TEST_TEXT_UPDATED in read_resp.text
report.add("更新笔记 - 内容已覆盖", content_updated,
f"内容长度={len(read_resp.text)}")
return code_ok and content_updated
except Exception as e:
return report.add("更新笔记", False, f"异常: {e}")
# ==================== 主流程 ====================
def main():
"""主测试流程"""
print("=" * 60)
print(" CTC笔记仓库 API 接口测试")
print(f" 基础URL: {BASE_URL}")
print(f" 测试时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
# 检查网络连通性
print("\n[预检] 测试网络连通性...")
try:
resp = requests.get(BASE_URL, timeout=TIMEOUT, allow_redirects=True)
print(f" 连接成功 (HTTP {resp.status_code})")
except Exception as e:
print(f" ❌ 无法连接到 {BASE_URL}: {e}")
print(" 请检查网络连接后重试。")
sys.exit(1)
# 初始化测试报告
report = TestReport()
# 生成测试用钥匙
key1 = generate_random_key(8)
key2 = generate_random_key(8)
keys_to_cleanup = [key1, key2] # 需要清理的笔记列表
print(f"\n 测试钥匙1: {key1}")
print(f" 测试钥匙2: {key2}")
# ---- 执行测试 ----
# 1. 写入笔记
test_write_note(report, key1)
time.sleep(0.3)
# 2. 读取笔记
test_read_note(report, key1)
time.sleep(0.3)
# 2补充. 读取不存在的笔记
test_read_nonexistent_note(report)
time.sleep(0.3)
# 3. 获取笔记信息
test_get_note_info(report, key1)
time.sleep(0.3)
# 3补充. 获取不存在笔记的信息
test_get_nonexistent_note_info(report)
time.sleep(0.3)
# 4. 批量检查变更
test_batch_check(report, key1, key2)
time.sleep(0.3)
# 补充. 更新已有笔记
test_update_note(report, key1)
time.sleep(0.3)
# 5. 创建随机笔记
success, new_key = test_create_random_note(report)
if new_key:
keys_to_cleanup.append(new_key)
time.sleep(0.3)
# 补充. 无效钥匙格式
test_invalid_key(report)
time.sleep(0.3)
# 6补充. 删除不存在的笔记
test_delete_nonexistent_note(report)
time.sleep(0.3)
# ---- 清理测试数据 ----
print_separator("清理测试数据")
for key in keys_to_cleanup:
print(f" 正在删除笔记: {key}...", end=" ")
try:
resp = requests.get(f"{BASE_URL}/?delete&note={key}", timeout=TIMEOUT)
data = safe_json_parse(resp)
if data.get("code") == 1:
print("✅ 已删除")
elif resp.status_code == 404:
print("⏭️ 不存在,跳过")
else:
msg = data.get("msg", resp.text[:50]) if data else resp.text[:50]
print(f"⚠️ 删除失败: {msg}")
except Exception as e:
print(f"❌ 异常: {e}")
time.sleep(0.2)
# ---- 验证清理结果 ----
print("\n[验证] 确认笔记已删除...")
verify_key = keys_to_cleanup[0]
try:
resp = requests.get(f"{BASE_URL}/{verify_key}?raw", timeout=TIMEOUT)
if resp.status_code == 404:
print(f" ✅ 笔记 {verify_key} 已确认删除 (404)")
else:
print(f" ⚠️ 笔记 {verify_key} 仍存在 (HTTP {resp.status_code})")
except Exception as e:
print(f" ❌ 验证异常: {e}")
# ---- 输出测试报告 ----
all_passed = report.summary()
# 返回退出码
sys.exit(0 if all_passed else 1)
if __name__ == "__main__":
main()