diff --git a/.gitignore b/.gitignore index d542d2d..c8bb84a 100644 --- a/.gitignore +++ b/.gitignore @@ -290,12 +290,67 @@ build/ # 测试和示例文件 test_*.py *_test.py +test_*.html +*_test.html +test_*.js +*_test.js example_*.py *_example.py demo_*.py *_demo.py fix_*.py *_fix.py +sample_*.py +*_sample.py +mock_*.py +*_mock.py +debug_*.py +*_debug.py + +# 测试相关目录 +tests/ +test/ +testing/ +__tests__/ +spec/ +specs/ + +# 测试配置文件 +pytest.ini +.pytest_cache/ +test_*.ini +*_test.ini +test_*.conf +*_test.conf + +# 测试数据文件 +test_*.json +*_test.json +test_*.csv +*_test.csv +test_*.xml +*_test.xml +test_*.xlsx +*_test.xlsx +test_*.txt +*_test.txt + +# 测试输出文件 +test_output/ +test_results/ +test_reports/ +coverage_html/ +.coverage +coverage.xml +htmlcov/ + +# 性能测试文件 +benchmark_*.py +*_benchmark.py +perf_*.py +*_perf.py +load_test_*.py +*_load_test.py # 文档文件(除了README.md) *.md @@ -339,7 +394,6 @@ setup.cfg tox.ini # 容器相关 -.dockerignore docker-compose.*.yml !docker-compose.yml !docker-compose-cn.yml @@ -385,4 +439,182 @@ systemd/ # 备份和归档 archive/ old/ -deprecated/ \ No newline at end of file +deprecated/ + +# ==================== 项目特定新增 ==================== +# 数据库文件 +xianyu_data.db +xianyu_data_backup_*.db + +# 实时日志文件 +realtime.log + +# 用户统计文件 +user_stats.db +user_stats.txt +stats.log + +# PHP测试文件 +php/ + +# 检查脚本 +check_disk_usage.py + +# Docker相关 +docker-compose.override.yml +.env.docker + +# IDE和编辑器 +.vscode/settings.json +.idea/workspace.xml +*.sublime-project +*.sublime-workspace + +# 操作系统特定 +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +desktop.ini +$RECYCLE.BIN/ + +# 网络和缓存 +.wget-hsts +.curl_sslcache + +# 临时和锁文件 +*.lock +*.pid +*.sock +*.port +.fuse_hidden* + +# 压缩和打包文件 +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# 媒体文件(如果不需要版本控制) +*.mp4 +*.avi +*.mov +*.wmv +*.flv +*.webm +*.mp3 +*.wav +*.flac +*.aac + +# 大文件和二进制文件 +*.bin +*.exe +*.dll +*.so +*.dylib + +# 文档生成 +docs/_build/ +docs/build/ +site/ +_site/ + +# 包管理器锁文件 +package-lock.json +yarn.lock +Pipfile.lock +poetry.lock + +# 环境和配置文件 +.env +.env.* +!.env.example +config.local.* +settings.local.* + +# 运行时生成的文件 +*.generated.* +*.auto.* +auto_* + +# 性能分析和调试 +*.prof +*.pstats +*.trace +*.debug +profile_* +debug_* + +# 安全相关 +*.key +*.pem +*.crt +*.cert +*.p12 +*.pfx +*.secret +*.token +*.auth +secrets/ +credentials/ +keys/ + +# 监控和统计 +monitoring/ +metrics/ +stats/ + +# 第三方工具 +.sonarqube/ +.scannerwork/ +.nyc_output/ +coverage/ +.coverage.* + +# 移动端开发 +*.apk +*.ipa +*.app +*.aab + +# 游戏开发 +*.unity +*.unitypackage + +# 科学计算 +*.mat +*.h5 +*.hdf5 + +# 地理信息系统 +*.shp +*.dbf +*.shx +*.prj + +# 3D模型 +*.obj +*.fbx +*.dae +*.3ds + +# 字体文件(如果不需要版本控制) +*.ttf +*.otf +*.woff +*.woff2 +*.eot + +# 数据文件(根据需要调整) +*.csv.bak +*.json.bak +*.xml.bak +*.sql.bak \ No newline at end of file diff --git a/README.md b/README.md index 5e60e3a..1cd9544 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,30 @@ 我用夸克网盘给你分享了「自动发货系统源码」,点击链接或复制整段内容,打开「夸克APP」即可获取。 /~5e4237vG5B~:/ 链接:https://pan.quark.cn/s/88d118bd700e +## 📋 项目概述 + +一个功能完整的闲鱼自动回复和管理系统,采用现代化的技术架构,支持多用户、多账号管理,具备智能回复、自动发货、自动确认发货、商品管理等企业级功能。系统基于Python异步编程,使用FastAPI提供RESTful API,SQLite数据库存储,支持Docker一键部署。 + > **⚠️ 重要提示:本项目仅供学习研究使用,严禁商业用途!使用前请仔细阅读[版权声明](#️-版权声明与使用条款)。** -一个功能完整的闲鱼自动回复和管理系统,支持多用户、多账号管理,具备智能回复、自动发货、自动确认发货、商品管理等企业级功能。 +## 🏗️ 技术架构 + +### 核心技术栈 +- **后端框架**: FastAPI + Python 3.11+ 异步编程 +- **数据库**: SQLite 3 + 多用户数据隔离 + 自动迁移 +- **前端**: Bootstrap 5 + Vanilla JavaScript + 响应式设计 +- **通信协议**: WebSocket + RESTful API + 实时通信 +- **部署方式**: Docker + Docker Compose + 一键部署 +- **日志系统**: Loguru + 文件轮转 + 实时收集 +- **安全认证**: JWT + 图形验证码 + 邮箱验证 + 权限控制 + +### 系统架构特点 +- **微服务设计**: 模块化架构,易于维护和扩展 +- **异步处理**: 基于asyncio的高性能异步处理 +- **多用户隔离**: 完全的数据隔离和权限控制 +- **容器化部署**: Docker容器化,支持一键部署 +- **实时监控**: WebSocket实时通信和状态监控 +- **自动化运维**: 自动重连、异常恢复、日志轮转 ## ✨ 核心特性 @@ -103,11 +124,12 @@ xianyu-auto-reply/ │ ├── xianyu_utils.py # 闲鱼API工具函数(加密、签名、解析) │ ├── message_utils.py # 消息格式化和处理工具 │ ├── ws_utils.py # WebSocket客户端封装 -│ ├── qr_login.py # 二维码登录功能 +│ ├── image_utils.py # 图片处理和管理工具 +│ ├── image_uploader.py # 图片上传到闲鱼CDN +│ ├── image_utils.py # 图片处理工具(压缩、格式转换) │ ├── item_search.py # 商品搜索功能(基于Playwright,无头模式) │ ├── order_detail_fetcher.py # 订单详情获取工具 -│ ├── image_utils.py # 图片处理工具(压缩、格式转换) -│ └── image_uploader.py # 图片上传到CDN工具 +│ └── qr_login.py # 二维码登录功能 ├── 🌐 前端界面 │ └── static/ │ ├── index.html # 主管理界面(集成所有功能模块) @@ -780,9 +802,88 @@ powershell -ExecutionPolicy Bypass -File docker-deploy.bat --- +## 📊 项目统计 + +- **代码行数**: 10,000+ 行 +- **功能模块**: 15+ 个核心模块 +- **API接口**: 50+ 个RESTful接口 +- **数据库表**: 20+ 个数据表 +- **支持平台**: Windows/Linux/macOS +- **部署方式**: Docker一键部署 +- **开发周期**: 持续更新维护 + +## 🎯 项目优势 + +### 技术优势 +- ✅ **现代化架构**: 基于FastAPI + Python 3.11+异步编程 +- ✅ **容器化部署**: Docker + Docker Compose一键部署 +- ✅ **多用户系统**: 完整的用户注册、登录、权限管理 +- ✅ **数据隔离**: 每个用户的数据完全独立,安全可靠 +- ✅ **实时通信**: WebSocket实时消息处理和状态监控 + +### 功能优势 +- ✅ **智能回复**: 关键词匹配 + AI智能回复 + 优先级策略 +- ✅ **自动发货**: 多种发货方式,支持规格匹配和延时发货 +- ✅ **商品管理**: 自动收集商品信息,支持批量操作 +- ✅ **订单管理**: 订单详情获取,支持自动确认发货 +- ✅ **安全保护**: 多层加密,防重复机制,异常恢复 + +### 运维优势 +- ✅ **日志系统**: 完整的日志记录和实时查看 +- ✅ **监控告警**: 账号状态监控和异常告警 +- ✅ **数据备份**: 自动数据备份和恢复机制 +- ✅ **性能优化**: 异步处理,高并发支持 +- ✅ **易于维护**: 模块化设计,代码结构清晰 +- ✅ **使用统计**: 匿名使用统计,帮助改进产品 + +## 📊 用户统计说明 + +### 统计目的 +为了了解有多少人在使用这个系统,系统会发送匿名的用户统计信息。 + +### 收集的信息 +- **匿名ID**: 基于机器特征生成的唯一标识符(重启不变) +- **操作系统**: 系统类型(如Windows、Linux) +- **版本信息**: 软件版本号 + +### 隐私保护 +- ✅ **完全匿名**: 不收集任何个人身份信息 +- ✅ **数据安全**: 不收集账号、密码、关键词等敏感信息 +- ✅ **本地优先**: 所有业务数据仅存储在本地 +- ✅ **持久化ID**: Docker重建时ID不变(保存在数据库中) + +### 查看统计信息 + +#### 方式1: Python统计服务器 +```bash +# 部署Python统计服务器 +python simple_stats_server.py + +# 访问统计服务器查看用户数量 +curl http://localhost:8081/stats +``` + +#### 方式2: PHP统计服务器 +```bash +# 将index.php部署到Web服务器(如Apache/Nginx) +# 访问统计接口 +curl http://localhost/php/stats + +# 测试统计功能 +python test_php_stats.py +``` + +**PHP统计服务器特点**: +- 数据保存在`user_stats.txt`文件中 +- 支持用户数据更新(anonymous_id作为key) +- 自动生成统计摘要 +- 记录操作日志到`stats.log` + +--- + 🎉 **开始使用闲鱼自动回复系统,让您的闲鱼店铺管理更加智能高效!** -**请记住:仅限学习使用,禁止商业用途!** +**⚠️ 重要提醒:本项目仅供学习研究使用,严禁商业用途!** ## Star History diff --git a/Start.py b/Start.py index 313adfb..e6c968b 100644 --- a/Start.py +++ b/Start.py @@ -27,6 +27,7 @@ from config import AUTO_REPLY, COOKIES_LIST import cookie_manager as cm from db_manager import db_manager from file_log_collector import setup_file_logging +from usage_statistics import report_user_count def _start_api_server(): @@ -143,6 +144,12 @@ async def main(): threading.Thread(target=_start_api_server, daemon=True).start() print("API 服务线程已启动") + # 上报用户统计 + try: + await report_user_count() + except Exception as e: + logger.debug(f"上报用户统计失败: {e}") + # 阻塞保持运行 print("主程序启动完成,保持运行...") await asyncio.Event().wait() diff --git a/db_manager.py b/db_manager.py index a5ca8fb..b126772 100644 --- a/db_manager.py +++ b/db_manager.py @@ -1339,6 +1339,29 @@ class DBManager: try: cursor = self.conn.cursor() + # 检查是否与现有图片关键词冲突 + for keyword, reply, item_id in keywords: + normalized_item_id = item_id if item_id and item_id.strip() else None + + # 检查是否存在同名的图片关键词 + if normalized_item_id: + # 有商品ID的情况:检查 (cookie_id, keyword, item_id) 是否存在图片关键词 + self._execute_sql(cursor, + "SELECT type FROM keywords WHERE cookie_id = ? AND keyword = ? AND item_id = ? AND type = 'image'", + (cookie_id, keyword, normalized_item_id)) + else: + # 通用关键词的情况:检查 (cookie_id, keyword) 是否存在图片关键词 + self._execute_sql(cursor, + "SELECT type FROM keywords WHERE cookie_id = ? AND keyword = ? AND (item_id IS NULL OR item_id = '') AND type = 'image'", + (cookie_id, keyword)) + + if cursor.fetchone(): + # 存在同名图片关键词,抛出友好的错误信息 + item_desc = f"商品ID: {normalized_item_id}" if normalized_item_id else "通用关键词" + error_msg = f"关键词 '{keyword}' ({item_desc}) 已存在(图片关键词),无法保存为文本关键词" + logger.warning(f"文本关键词与图片关键词冲突: Cookie={cookie_id}, 关键词='{keyword}', {item_desc}") + raise ValueError(error_msg) + # 只删除该cookie_id的文本类型关键字,保留图片关键词 self._execute_sql(cursor, "DELETE FROM keywords WHERE cookie_id = ? AND (type IS NULL OR type = 'text')", @@ -1349,22 +1372,15 @@ class DBManager: # 标准化item_id:空字符串转为NULL normalized_item_id = item_id if item_id and item_id.strip() else None - try: - self._execute_sql(cursor, - "INSERT INTO keywords (cookie_id, keyword, reply, item_id, type) VALUES (?, ?, ?, ?, 'text')", - (cookie_id, keyword, reply, normalized_item_id)) - except sqlite3.IntegrityError as ie: - # 如果遇到唯一约束冲突,记录详细错误信息并回滚 - item_desc = f"商品ID: {normalized_item_id}" if normalized_item_id else "通用关键词" - logger.error(f"关键词唯一约束冲突: Cookie={cookie_id}, 关键词='{keyword}', {item_desc}") - self.conn.rollback() - raise ie + self._execute_sql(cursor, + "INSERT INTO keywords (cookie_id, keyword, reply, item_id, type) VALUES (?, ?, ?, ?, 'text')", + (cookie_id, keyword, reply, normalized_item_id)) self.conn.commit() logger.info(f"文本关键字保存成功: {cookie_id}, {len(keywords)}条,图片关键词已保留") return True - except sqlite3.IntegrityError: - # 唯一约束冲突,重新抛出异常让上层处理 + except ValueError: + # 重新抛出友好的错误信息 raise except Exception as e: logger.error(f"文本关键字保存失败: {e}") diff --git a/quick_test_api.py b/quick_test_api.py deleted file mode 100644 index 55bff76..0000000 --- a/quick_test_api.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -快速测试QQ回复消息API的脚本 -""" - -import requests -import json - -def test_api(api_key, cookie_id="test", chat_id="test", to_user_id="test", message="test"): - """测试API调用""" - url = "http://localhost:8000/send-message" - - data = { - "api_key": api_key, - "cookie_id": cookie_id, - "chat_id": chat_id, - "to_user_id": to_user_id, - "message": message - } - - try: - response = requests.post(url, json=data, timeout=10) - result = response.json() - - print(f"秘钥: {api_key}") - print(f"状态: {response.status_code}") - print(f"响应: {json.dumps(result, ensure_ascii=False, indent=2)}") - print("-" * 50) - - return result.get("success", False) - - except Exception as e: - print(f"请求失败: {e}") - return False - -def main(): - print("🚀 快速API测试") - print("=" * 50) - - # 测试用例 - test_cases = [ - ("默认秘钥", "xianyu_qq_reply_2024"), - ("测试秘钥", "zhinina_test_key"), - ("错误秘钥", "wrong_key"), - ("空秘钥", ""), - ] - - for name, key in test_cases: - print(f"\n📋 测试: {name}") - test_api(key) - - # 测试参数验证 - print("\n📋 测试参数验证:") - - # 测试空参数 - param_tests = [ - ("空cookie_id", {"cookie_id": ""}), - ("空chat_id", {"chat_id": ""}), - ("空to_user_id", {"to_user_id": ""}), - ("空message", {"message": ""}), - ] - - for name, params in param_tests: - print(f"\n测试: {name}") - default_params = { - "cookie_id": "test", - "chat_id": "test", - "to_user_id": "test", - "message": "test" - } - default_params.update(params) - test_api("xianyu_qq_reply_2024", **default_params) - -if __name__ == "__main__": - main() diff --git a/reply_server.py b/reply_server.py index 9f62aa1..140b1da 100644 --- a/reply_server.py +++ b/reply_server.py @@ -24,6 +24,7 @@ from ai_reply_engine import ai_reply_engine from utils.qr_login import qr_login_manager from utils.xianyu_utils import trans_cookies from utils.image_utils import image_manager + from loguru import logger # 关键字文件路径 @@ -2352,12 +2353,40 @@ def update_keywords_with_item_id(cid: str, body: KeywordWithItemIdIn, current_us raise HTTPException(status_code=500, detail="保存关键词失败") except Exception as e: error_msg = str(e) - if "UNIQUE constraint failed" in error_msg: - # 解析具体的冲突信息 - if "keywords.cookie_id, keywords.keyword" in error_msg: - raise HTTPException(status_code=400, detail="关键词重复!该关键词已存在(可能是图片关键词或文本关键词),请使用其他关键词") + + # 检查是否是图片关键词冲突 + if "已存在(图片关键词)" in error_msg: + # 直接使用数据库管理器提供的友好错误信息 + raise HTTPException(status_code=400, detail=error_msg) + elif "UNIQUE constraint failed" in error_msg or "唯一约束冲突" in error_msg: + # 尝试从错误信息中提取具体的冲突关键词 + conflict_keyword = None + conflict_type = None + + # 检查是否是数据库管理器抛出的详细错误 + if "关键词唯一约束冲突" in error_msg: + # 解析详细错误信息:关键词唯一约束冲突: Cookie=xxx, 关键词='xxx', 通用关键词/商品ID: xxx + import re + keyword_match = re.search(r"关键词='([^']+)'", error_msg) + if keyword_match: + conflict_keyword = keyword_match.group(1) + + if "通用关键词" in error_msg: + conflict_type = "通用关键词" + elif "商品ID:" in error_msg: + item_match = re.search(r"商品ID: ([^\s,]+)", error_msg) + if item_match: + conflict_type = f"商品关键词(商品ID: {item_match.group(1)})" + + # 构造用户友好的错误信息 + if conflict_keyword and conflict_type: + detail_msg = f'关键词 "{conflict_keyword}" ({conflict_type}) 已存在,请使用其他关键词或商品ID' + elif "keywords.cookie_id, keywords.keyword" in error_msg: + detail_msg = "关键词重复!该关键词已存在(可能是图片关键词或文本关键词),请使用其他关键词" else: - raise HTTPException(status_code=400, detail="关键词重复!请使用不同的关键词或商品ID组合") + detail_msg = "关键词重复!请使用不同的关键词或商品ID组合" + + raise HTTPException(status_code=400, detail=detail_msg) else: log_with_user('error', f"保存关键词时发生未知错误: {error_msg}", current_user) raise HTTPException(status_code=500, detail="保存关键词失败") @@ -4472,6 +4501,43 @@ def update_item_multi_quantity_delivery(cookie_id: str, item_id: str, delivery_d raise HTTPException(status_code=500, detail=str(e)) + + + +# ==================== 订单管理接口 ==================== + +@app.get('/api/orders') +def get_user_orders(current_user: Dict[str, Any] = Depends(get_current_user)): + """获取当前用户的订单信息""" + try: + from db_manager import db_manager + + user_id = current_user['user_id'] + log_with_user('info', "查询用户订单信息", current_user) + + # 获取用户的所有Cookie + user_cookies = db_manager.get_all_cookies(user_id) + + # 获取所有订单数据 + all_orders = [] + for cookie_id in user_cookies.keys(): + orders = db_manager.get_orders_by_cookie(cookie_id, limit=1000) # 增加限制数量 + # 为每个订单添加cookie_id信息 + for order in orders: + order['cookie_id'] = cookie_id + all_orders.append(order) + + # 按创建时间倒序排列 + all_orders.sort(key=lambda x: x.get('created_at', ''), reverse=True) + + log_with_user('info', f"用户订单查询成功,共 {len(all_orders)} 条记录", current_user) + return {"success": True, "data": all_orders} + + except Exception as e: + log_with_user('error', f"查询用户订单失败: {str(e)}", current_user) + raise HTTPException(status_code=500, detail=f"查询订单失败: {str(e)}") + + # 移除自动启动,由Start.py或手动启动 # if __name__ == "__main__": # uvicorn.run(app, host="0.0.0.0", port=8080) \ No newline at end of file diff --git a/simple_stats_server.py b/simple_stats_server.py new file mode 100644 index 0000000..0c6d485 --- /dev/null +++ b/simple_stats_server.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +简单的用户统计服务器 +只统计有多少人在使用闲鱼自动回复系统 +""" + +from fastapi import FastAPI +from pydantic import BaseModel +from typing import Dict, Any +import sqlite3 +from datetime import datetime +import uvicorn +from pathlib import Path + +app = FastAPI(title="闲鱼自动回复系统用户统计", version="1.0.0") + +# 数据库文件路径 +DB_PATH = Path(__file__).parent / "user_stats.db" + + +class UserStats(BaseModel): + """用户统计数据模型""" + anonymous_id: str + timestamp: str + project: str + info: Dict[str, Any] + + +def init_database(): + """初始化数据库""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # 创建用户统计表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS user_stats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + anonymous_id TEXT UNIQUE NOT NULL, + first_seen DATETIME DEFAULT CURRENT_TIMESTAMP, + last_seen DATETIME DEFAULT CURRENT_TIMESTAMP, + os TEXT, + version TEXT, + total_reports INTEGER DEFAULT 1 + ) + ''') + + # 创建索引 + cursor.execute('CREATE INDEX IF NOT EXISTS idx_anonymous_id ON user_stats(anonymous_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_last_seen ON user_stats(last_seen)') + + conn.commit() + conn.close() + + +def save_user_stats(data: UserStats): + """保存用户统计数据""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + try: + info = data.info + os_info = info.get('os', 'unknown') + version = info.get('version', '2.2.0') + + # 检查用户是否已存在 + cursor.execute('SELECT id, total_reports FROM user_stats WHERE anonymous_id = ?', (data.anonymous_id,)) + existing = cursor.fetchone() + + if existing: + # 更新现有用户的最后访问时间和报告次数 + cursor.execute(''' + UPDATE user_stats + SET last_seen = CURRENT_TIMESTAMP, + total_reports = total_reports + 1, + os = ?, + version = ? + WHERE anonymous_id = ? + ''', (os_info, version, data.anonymous_id)) + else: + # 插入新用户 + cursor.execute(''' + INSERT INTO user_stats (anonymous_id, os, version) + VALUES (?, ?, ?) + ''', (data.anonymous_id, os_info, version)) + + conn.commit() + return True + + except Exception as e: + print(f"保存用户统计失败: {e}") + return False + finally: + conn.close() + + +@app.post('/statistics') +async def receive_user_stats(data: UserStats): + """接收用户统计数据""" + try: + success = save_user_stats(data) + + if success: + print(f"收到用户统计: {data.anonymous_id}") + return {"status": "success", "message": "用户统计已收到"} + else: + return {"status": "error", "message": "保存统计数据失败"} + + except Exception as e: + print(f"处理用户统计失败: {e}") + return {"status": "error", "message": "处理统计数据失败"} + + +@app.get('/stats') +async def get_user_stats(): + """获取用户统计摘要""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + try: + # 总用户数 + cursor.execute('SELECT COUNT(*) FROM user_stats') + total_users = cursor.fetchone()[0] + + # 今日活跃用户 + cursor.execute(''' + SELECT COUNT(*) + FROM user_stats + WHERE DATE(last_seen) = DATE('now') + ''') + daily_active = cursor.fetchone()[0] + + # 本周活跃用户 + cursor.execute(''' + SELECT COUNT(*) + FROM user_stats + WHERE DATE(last_seen) >= DATE('now', '-7 days') + ''') + weekly_active = cursor.fetchone()[0] + + # 操作系统分布 + cursor.execute(''' + SELECT os, COUNT(*) as count + FROM user_stats + GROUP BY os + ORDER BY count DESC + ''') + os_distribution = dict(cursor.fetchall()) + + # 版本分布 + cursor.execute(''' + SELECT version, COUNT(*) as count + FROM user_stats + GROUP BY version + ORDER BY count DESC + ''') + version_distribution = dict(cursor.fetchall()) + + return { + "total_users": total_users, + "daily_active_users": daily_active, + "weekly_active_users": weekly_active, + "os_distribution": os_distribution, + "version_distribution": version_distribution, + "last_updated": datetime.now().isoformat() + } + + except Exception as e: + return {"error": f"获取统计失败: {e}"} + finally: + conn.close() + + +@app.get('/stats/recent') +async def get_recent_users(): + """获取最近活跃的用户(匿名)""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + try: + cursor.execute(''' + SELECT anonymous_id, first_seen, last_seen, os, version, total_reports + FROM user_stats + ORDER BY last_seen DESC + LIMIT 20 + ''') + + records = cursor.fetchall() + + return { + "recent_users": [ + { + "anonymous_id": record[0][:8] + "****", # 部分隐藏ID + "first_seen": record[1], + "last_seen": record[2], + "os": record[3], + "version": record[4], + "total_reports": record[5] + } + for record in records + ] + } + + except Exception as e: + return {"error": f"获取最近用户失败: {e}"} + finally: + conn.close() + + +@app.get('/') +async def root(): + """根路径""" + return { + "message": "闲鱼自动回复系统用户统计服务器", + "description": "只统计有多少人在使用这个系统", + "endpoints": { + "POST /statistics": "接收用户统计数据", + "GET /stats": "获取用户统计摘要", + "GET /stats/recent": "获取最近活跃用户" + } + } + + +if __name__ == "__main__": + # 初始化数据库 + init_database() + print("用户统计数据库初始化完成") + + # 启动服务器 + print("启动用户统计服务器...") + print("访问 http://localhost:8081/stats 查看统计信息") + uvicorn.run(app, host="0.0.0.0", port=8081) diff --git a/static/index.html b/static/index.html index 0d32cad..fa359c8 100644 --- a/static/index.html +++ b/static/index.html @@ -155,7 +155,11 @@
系统概览和统计信息
-