diff --git a/CLI_USAGE.md b/CLI_USAGE.md new file mode 100644 index 0000000..6e033ba --- /dev/null +++ b/CLI_USAGE.md @@ -0,0 +1,335 @@ +# 城市手册 - 命令行接口使用指南 + +## 概述 + +城市手册提供完整的命令行接口 (CLI),可以通过命令行操作所有核心功能。 + +## 运行方式 + +### 在服务器上运行 + +```bash +cd /home/ubuntu/city-manual +docker compose exec -T backend python /app/cli.py <命令> [参数] +``` + +### 本地运行(需要访问服务器数据库) + +```bash +cd /path/to/project +python cli.py <命令> [参数] +``` + +--- + +## 命令列表 + +### 认证命令 + +#### login - 登录获取 Token + +```bash +docker compose exec -T backend python /app/cli.py login <用户名> <密码> +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py login admin Admin123! +``` + +**输出:** +``` +✅ 登录成功 +Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +--- + +### 省份命令 + +#### provinces - 获取所有省份 + +```bash +docker compose exec -T backend python /app/cli.py provinces +``` + +**输出:** +``` +✅ 共 34 个省份: + 1. 北京市 (ID: 1) + 2. 天津市 (ID: 2) + 3. 上海市 (ID: 3) + ... +``` + +#### region - 获取省份详情 + +```bash +docker compose exec -T backend python /app/cli.py region <省份 ID> +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py region 1 +``` + +--- + +### 文章命令 + +#### article list - 获取文章列表 + +```bash +docker compose exec -T backend python /app/cli.py article list [数量] +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py article list 10 +``` + +#### article create - 创建文章 + +```bash +docker compose exec -T backend python /app/cli.py article create <标题> <内容> <省份 ID> [类型] +``` + +**类型选项:** `basic`, `history`, `culture`, `practical`, `life` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py article create "北京攻略" "北京是中国的首都..." 1 basic +``` + +#### article submit - 提交文章审核 + +```bash +docker compose exec -T backend python /app/cli.py article submit <文章 ID> +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py article submit 1 +``` + +--- + +### 服务命令 + +#### service list - 获取服务列表 + +```bash +docker compose exec -T backend python /app/cli.py service list [数量] +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py service list 10 +``` + +#### service create - 创建特色服务 + +```bash +docker compose exec -T backend python /app/cli.py service create <名称> <描述> <省份 ID> [分类] +``` + +**分类选项:** `clothing`, `food`, `accommodation`, `transport`, `entertainment`, `tourism`, `culture` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py service create "老北京烤鸭" "正宗北京烤鸭" 1 food +``` + +--- + +### AI 审核命令 🔥 + +#### audit status - AI 审核服务状态 + +```bash +docker compose exec -T backend python /app/cli.py audit status +``` + +**输出:** +``` +✅ AI 审核服务状态:active + 版本:1.0.0 + 功能:敏感词检测, 广告检测, 内容质量评估 +``` + +#### audit article - AI 审核文章 + +```bash +docker compose exec -T backend python /app/cli.py audit article <标题> <内容> +``` + +**示例 1 (正常内容):** +```bash +docker compose exec -T backend python /app/cli.py audit article "北京攻略" "北京是中国的首都,有很多著名景点" +``` + +**输出:** +``` +AI 审核结果:✅ 通过 + 原因:审核通过 + 详情:{ + "quality_score": 100 +} +``` + +**示例 2 (敏感词):** +```bash +docker compose exec -T backend python /app/cli.py audit article "测试" "这是一个包含暴力内容的文章" +``` + +**输出:** +``` +AI 审核结果:❌ 拒绝 + 原因:内容包含敏感词:暴力 + 详情:{ + "sensitive_words": [ + "暴力" + ] +} +``` + +#### audit comment - AI 审核评论 + +```bash +docker compose exec -T backend python /app/cli.py audit comment <内容> +``` + +**示例 1 (正常):** +```bash +docker compose exec -T backend python /app/cli.py audit comment "写得很好!" +``` + +**示例 2 (广告):** +```bash +docker compose exec -T backend python /app/cli.py audit comment "加微信 123456 了解更多" +``` + +**输出:** +``` +AI 审核结果:❌ 拒绝 + 原因:疑似广告:加微信 +``` + +#### audit service - AI 审核服务 + +```bash +docker compose exec -T backend python /app/cli.py audit service <名称> <描述> +``` + +**示例:** +```bash +docker compose exec -T backend python /app/cli.py audit service "老北京烤鸭" "正宗北京烤鸭,皮脆肉嫩" +``` + +--- + +## 快速测试脚本 + +### 测试所有 AI 审核功能 + +```bash +cd /home/ubuntu/city-manual + +echo '=== AI 审核测试套件 ===' + +echo '1. 测试正常文章' +docker compose exec -T backend python /app/cli.py audit article '北京攻略' '北京是中国的首都' + +echo '' +echo '2. 测试敏感词文章' +docker compose exec -T backend python /app/cli.py audit article '测试' '包含暴力内容' + +echo '' +echo '3. 测试广告评论' +docker compose exec -T backend python /app/cli.py audit comment '加微信 123456' + +echo '' +echo '4. 测试正常服务' +docker compose exec -T backend python /app/cli.py audit service '烤鸭店' '正宗北京烤鸭' + +echo '' +echo '=== 测试完成 ===' +``` + +--- + +## 完整工作流程示例 + +### 1. 创建并审核文章 + +```bash +# 查看省份列表 +docker compose exec -T backend python /app/cli.py provinces + +# 创建文章(使用北京市 ID=1) +docker compose exec -T backend python /app/cli.py article create "北京旅游攻略" "北京是中国的首都..." 1 + +# AI 预审 +docker compose exec -T backend python /app/cli.py audit article "北京旅游攻略" "北京是中国的首都..." + +# 提交审核 +docker compose exec -T backend python /app/cli.py article submit 1 +``` + +### 2. 创建并审核服务 + +```bash +# 创建服务 +docker compose exec -T backend python /app/cli.py service create "老北京烤鸭" "正宗北京烤鸭" 1 food + +# AI 预审 +docker compose exec -T backend python /app/cli.py audit service "老北京烤鸭" "正宗北京烤鸭" +``` + +--- + +## 错误处理 + +### 常见错误 + +1. **认证失败** + ``` + ❌ 错误:Authentication credentials were not provided. + ``` + 解决:确保使用正确的用户名密码登录 + +2. **网络错误** + ``` + ❌ 错误:network - + ``` + 解决:检查 Docker 容器是否正常运行 + +3. **内容被拒绝** + ``` + AI 审核结果:❌ 拒绝 + 原因:内容包含敏感词:暴力 + ``` + 解决:修改内容,移除敏感词 + +--- + +## 系统状态检查 + +```bash +# 检查容器状态 +docker compose ps + +# 检查数据库 +docker compose exec -T backend python /app/cli.py provinces + +# 检查 AI 审核服务 +docker compose exec -T backend python /app/cli.py audit status +``` + +--- + +## 文档版本 + +- **版本**: 1.0.0 +- **更新日期**: 2026-04-14 +- **测试状态**: ✅ 所有命令测试通过 diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..5fe6328 --- /dev/null +++ b/cli.py @@ -0,0 +1,422 @@ +#!/usr/bin/env python3 +""" +城市手册 - 命令行接口工具 + +用法: + python cli.py <命令> [参数] + +示例: + python cli.py login admin Admin123! + python cli.py provinces + python cli.py article list + python cli.py article create "标题" "内容" 1 + python cli.py audit article "标题" "内容" +""" + +import os +import sys +import json +import urllib.request +import urllib.error + +# Django 设置 +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod') + +import django +django.setup() + +from django.contrib.auth import get_user_model +from rest_framework_simplejwt.tokens import RefreshToken + +# 配置 +BASE_URL = 'http://localhost:8000' +ACCESS_TOKEN = None + + +def get_token(username='admin'): + """获取 JWT Token""" + User = get_user_model() + user = User.objects.filter(username=username).first() + if not user: + print(f'❌ 用户 {username} 不存在') + return None + + refresh = RefreshToken.for_user(user) + return str(refresh.access_token) + + +def api_request(endpoint, method='GET', data=None, token=None): + """发送 API 请求""" + url = f'{BASE_URL}{endpoint}' + headers = {'Content-Type': 'application/json'} + + if token: + headers['Authorization'] = f'Bearer {token}' + + if data: + data = json.dumps(data, ensure_ascii=False).encode('utf-8') + + req = urllib.request.Request(url, data=data, headers=headers, method=method) + + try: + with urllib.request.urlopen(req, timeout=10) as response: + return json.loads(response.read().decode()) + except urllib.error.HTTPError as e: + error_body = e.read().decode() + try: + error_data = json.loads(error_body) + return {'error': e.code, 'message': error_data} + except: + return {'error': e.code, 'message': error_body} + except Exception as e: + return {'error': 'network', 'message': str(e)} + + +def cmd_login(username, password): + """登录获取 Token""" + result = api_request('/api/auth/login/', 'POST', { + 'username': username, + 'password': password + }) + + if 'access' in result: + print(f'✅ 登录成功') + print(f'Token: {result["access"][:50]}...') + return result['access'] + else: + print(f'❌ 登录失败:{result}') + return None + + +def cmd_provinces(): + """获取所有省份""" + result = api_request('/api/regions/provinces/') + + if isinstance(result, list): + print(f'✅ 共 {len(result)} 个省份:') + for i, p in enumerate(result, 1): + print(f' {i}. {p["name"]} (ID: {p["id"]})') + return result + else: + print(f'❌ 获取失败:{result}') + return None + + +def cmd_region_detail(region_id): + """获取省份详情""" + result = api_request(f'/api/regions/{region_id}/') + + if isinstance(result, dict) and 'id' in result: + print(f'✅ 省份信息:') + print(f' 名称:{result.get("name")}') + print(f' 级别:{result.get("level")}') + print(f' 状态:{result.get("status")}') + return result + else: + print(f'❌ 获取失败:{result}') + return None + + +def cmd_article_list(limit=10): + """获取文章列表""" + token = get_token() + result = api_request(f'/api/articles/?limit={limit}', token=token) + + if isinstance(result, dict) and 'results' in result: + articles = result['results'] + print(f'✅ 共 {result.get("count", 0)} 篇文章:') + for i, a in enumerate(articles, 1): + print(f' {i}. [{a.get("id")}] {a.get("title")} - {a.get("publish_status")}') + return result + else: + print(f'❌ 获取失败:{result}') + return None + + +def cmd_article_create(title, content, region_id, article_type='basic'): + """创建文章""" + token = get_token() + if not token: + return None + + result = api_request('/api/articles/', 'POST', { + 'title': title, + 'content': content, + 'region': region_id, + 'article_type': article_type + }, token=token) + + if 'id' in result: + print(f'✅ 文章创建成功 (ID: {result["id"]})') + print(f' 标题:{result.get("title")}') + print(f' 状态:{result.get("publish_status")}') + return result + else: + print(f'❌ 创建失败:{result}') + return None + + +def cmd_article_submit(article_id): + """提交文章审核""" + token = get_token() + if not token: + return None + + result = api_request(f'/api/articles/{article_id}/submit/', 'POST', {}, token=token) + + if 'id' in result: + print(f'✅ 文章已提交审核 (ID: {article_id})') + print(f' 版主审核状态:{result.get("moderator_status")}') + print(f' AI 审核状态:{result.get("ai_status")}') + return result + else: + print(f'❌ 提交失败:{result}') + return None + + +def cmd_audit_article(title, content): + """AI 审核文章""" + token = get_token() + if not token: + return None + + result = api_request('/api/audit/article/', 'POST', { + 'title': title, + 'content': content + }, token=token) + + status = '✅ 通过' if result.get('approved') else '❌ 拒绝' + print(f'AI 审核结果:{status}') + print(f' 原因:{result.get("reason")}') + if 'details' in result: + print(f' 详情:{json.dumps(result["details"], ensure_ascii=False, indent=2)}') + return result + + +def cmd_audit_comment(content): + """AI 审核评论""" + token = get_token() + if not token: + return None + + result = api_request('/api/audit/comment/', 'POST', { + 'content': content + }, token=token) + + status = '✅ 通过' if result.get('approved') else '❌ 拒绝' + print(f'AI 审核结果:{status}') + print(f' 原因:{result.get("reason")}') + return result + + +def cmd_audit_service(name, description): + """AI 审核服务""" + token = get_token() + if not token: + return None + + result = api_request('/api/audit/service/', 'POST', { + 'name': name, + 'description': description + }, token=token) + + status = '✅ 通过' if result.get('approved') else '❌ 拒绝' + print(f'AI 审核结果:{status}') + print(f' 原因:{result.get("reason")}') + return result + + +def cmd_audit_status(): + """AI 审核服务状态""" + token = get_token() + result = api_request('/api/audit/status/', token=token) + + if 'status' in result: + print(f'✅ AI 审核服务状态:{result["status"]}') + print(f' 版本:{result.get("version")}') + print(f' 功能:{", ".join(result.get("features", []))}') + return result + else: + print(f'❌ 获取失败:{result}') + return None + + +def cmd_service_list(limit=10): + """获取服务列表""" + token = get_token() + result = api_request(f'/api/services/?limit={limit}', token=token) + + if isinstance(result, dict) and 'results' in result: + services = result['results'] + print(f'✅ 共 {result.get("count", 0)} 个服务:') + for i, s in enumerate(services, 1): + print(f' {i}. [{s.get("id")}] {s.get("name")} - {s.get("category")}') + return result + else: + print(f'❌ 获取失败:{result}') + return None + + +def cmd_service_create(name, description, region_id, category='food'): + """创建特色服务""" + token = get_token() + if not token: + return None + + result = api_request('/api/services/', 'POST', { + 'name': name, + 'description': description, + 'region': region_id, + 'category': category + }, token=token) + + if 'id' in result: + print(f'✅ 服务创建成功 (ID: {result["id"]})') + print(f' 名称:{result.get("name")}') + print(f' 分类:{result.get("category")}') + return result + else: + print(f'❌ 创建失败:{result}') + return None + + +def cmd_help(): + """显示帮助信息""" + help_text = """ +城市手册 - 命令行接口 + +用法:python cli.py <命令> [参数] + +认证命令: + login <用户名> <密码> 登录获取 Token + +省份命令: + provinces 获取所有省份 + region 获取省份详情 + +文章命令: + article list [limit] 获取文章列表 + article create <标题> <内容> <省份 ID> [类型] + 创建文章 + article submit 提交文章审核 + +服务命令: + service list [limit] 获取服务列表 + service create <名称> <描述> <省份 ID> [分类] + 创建服务 + +AI 审核命令: + audit article <标题> <内容> AI 审核文章 + audit comment <内容> AI 审核评论 + audit service <名称> <描述> AI 审核服务 + audit status AI 审核服务状态 + +示例: + python cli.py login admin Admin123! + python cli.py provinces + python cli.py article create "北京攻略" "北京是首都..." 1 + python cli.py audit article "测试" "包含暴力内容" + python cli.py audit status +""" + print(help_text) + + +def main(): + if len(sys.argv) < 2: + cmd_help() + return + + command = sys.argv[1] + + # 登录命令 + if command == 'login': + if len(sys.argv) < 4: + print('用法:python cli.py login <用户名> <密码>') + return + cmd_login(sys.argv[2], sys.argv[3]) + + # 省份命令 + elif command == 'provinces': + cmd_provinces() + + elif command == 'region': + if len(sys.argv) < 3: + print('用法:python cli.py region ') + return + cmd_region_detail(sys.argv[2]) + + # 文章命令 + elif command == 'article': + if len(sys.argv) < 3: + print('用法:python cli.py article [参数]') + return + + subcommand = sys.argv[2] + if subcommand == 'list': + limit = sys.argv[3] if len(sys.argv) > 3 else 10 + cmd_article_list(limit) + elif subcommand == 'create': + if len(sys.argv) < 6: + print('用法:python cli.py article create <标题> <内容> <省份 ID> [类型]') + return + cmd_article_create(sys.argv[3], sys.argv[4], sys.argv[5], + sys.argv[6] if len(sys.argv) > 6 else 'basic') + elif subcommand == 'submit': + if len(sys.argv) < 4: + print('用法:python cli.py article submit ') + return + cmd_article_submit(sys.argv[3]) + + # 服务命令 + elif command == 'service': + if len(sys.argv) < 3: + print('用法:python cli.py service [参数]') + return + + subcommand = sys.argv[2] + if subcommand == 'list': + limit = sys.argv[3] if len(sys.argv) > 3 else 10 + cmd_service_list(limit) + elif subcommand == 'create': + if len(sys.argv) < 6: + print('用法:python cli.py service create <名称> <描述> <省份 ID> [分类]') + return + cmd_service_create(sys.argv[3], sys.argv[4], sys.argv[5], + sys.argv[6] if len(sys.argv) > 6 else 'food') + + # AI 审核命令 + elif command == 'audit': + if len(sys.argv) < 3: + print('用法:python cli.py audit [参数]') + return + + subcommand = sys.argv[2] + if subcommand == 'article': + if len(sys.argv) < 5: + print('用法:python cli.py audit article <标题> <内容>') + return + cmd_audit_article(sys.argv[3], sys.argv[4]) + elif subcommand == 'comment': + if len(sys.argv) < 4: + print('用法:python cli.py audit comment <内容>') + return + cmd_audit_comment(sys.argv[3]) + elif subcommand == 'service': + if len(sys.argv) < 5: + print('用法:python cli.py audit service <名称> <描述>') + return + cmd_audit_service(sys.argv[3], sys.argv[4]) + elif subcommand == 'status': + cmd_audit_status() + + # 帮助命令 + elif command == 'help' or command == '--help' or command == '-h': + cmd_help() + + else: + print(f'❌ 未知命令:{command}') + print('使用 python cli.py help 查看帮助') + + +if __name__ == '__main__': + main()