ZHIWEI666 commited on
Commit
be75899
·
verified ·
1 Parent(s): 722430c

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +10 -0
  2. app.py +191 -0
  3. requirements.txt +5 -0
  4. 数据库连接.py +57 -0
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
7
+
8
+ COPY . .
9
+
10
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ⚙️ 后端逻辑/核心服务端.py
2
+ from fastapi import FastAPI, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel
5
+ from typing import Optional
6
+ import time
7
+ import uuid
8
+
9
+ # 导入刚刚写好的数据库操作模块
10
+ import 数据库连接 as db
11
+
12
+ app = FastAPI(title="ComfyUI Ranking Community API")
13
+
14
+ # 允许跨域请求,这是 ComfyUI 能够访问云端的关键
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_credentials=True,
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ # --- 增加根目录探针,防止 Hugging Face 健康检查失败 ---
24
+ @app.get("/")
25
+ def read_root():
26
+ return {"status": "ok", "message": "ComfyUI Ranking API is running!"}
27
+
28
+ # --- 数据模型 ---
29
+ class UserRegister(BaseModel):
30
+ name: str
31
+ gender: str
32
+ avatarDataUrl: Optional[str] = None
33
+ age: Optional[int] = None
34
+ country: Optional[str] = None
35
+ region: Optional[str] = None
36
+ intro: Optional[str] = None
37
+
38
+ class InteractionToggle(BaseModel):
39
+ item_id: str
40
+ user_id: str # 使用 name 作为唯一标识 (简化版)
41
+ action_type: str # "like", "favorite"
42
+ is_active: bool
43
+
44
+ class CommentCreate(BaseModel):
45
+ item_id: str
46
+ author: str
47
+ content: str
48
+ reply_to_user: Optional[str] = None
49
+ parent_id: Optional[str] = None # 如果是回复,记录父评论ID
50
+
51
+ # --- 1. 用户系统 ---
52
+ @app.post("/api/users/register")
53
+ async def register_user(user: UserRegister):
54
+ users_db = db.load_data("users.json", default_data={})
55
+
56
+ if user.name in users_db:
57
+ raise HTTPException(status_code=400, detail="该用户名已被注册")
58
+
59
+ # 构建用户档案,初始化各项统计为 0
60
+ new_user = user.dict()
61
+ new_user.update({
62
+ "receivedLikes": 0, "receivedFollows": 0,
63
+ "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}
64
+ })
65
+
66
+ users_db[user.name] = new_user
67
+ db.save_data("users.json", users_db)
68
+
69
+ return {"status": "success", "message": "注册成功", "data": new_user}
70
+
71
+ @app.get("/api/users/{username}")
72
+ async def get_user_profile(username: str):
73
+ users_db = db.load_data("users.json", default_data={})
74
+ if username not in users_db:
75
+ raise HTTPException(status_code=404, detail="用户不存在")
76
+ return {"status": "success", "data": users_db[username]}
77
+
78
+ # --- 2. 排行榜核心数据流 ---
79
+ @app.get("/api/items")
80
+ async def get_items(type: str = "tool", sort: str = "time", limit: int = 20):
81
+ items_db = db.load_data("items.json", default_data=[])
82
+
83
+ # 过滤类型
84
+ filtered_items = [item for item in items_db if item.get("type") == type]
85
+
86
+ # 排序逻辑
87
+ if sort == "likes":
88
+ filtered_items.sort(key=lambda x: x.get("likes", 0), reverse=True)
89
+ elif sort == "favorites":
90
+ filtered_items.sort(key=lambda x: x.get("favorites", 0), reverse=True)
91
+ elif sort == "downloads":
92
+ filtered_items.sort(key=lambda x: x.get("uses", 0), reverse=True)
93
+ else: # time (默认)
94
+ filtered_items.sort(key=lambda x: x.get("created_at", 0), reverse=True)
95
+
96
+ return {"status": "success", "data": filtered_items[:limit]}
97
+
98
+ # --- 3. 社交与互动 (点赞/收藏防抖与排重) ---
99
+ @app.post("/api/interactions/toggle")
100
+ async def toggle_interaction(interaction: InteractionToggle):
101
+ items_db = db.load_data("items.json", default_data=[])
102
+
103
+ target_item = next((item for item in items_db if item["id"] == interaction.item_id), None)
104
+ if not target_item:
105
+ raise HTTPException(status_code=404, detail="目标工具/应用不存在")
106
+
107
+ # 确定要操作的列表字段,例如 "liked_by" 或 "favorited_by"
108
+ list_key = f"{interaction.action_type}d_by" # liked_by / favorited_by
109
+ count_key = "likes" if interaction.action_type == "like" else "favorites"
110
+
111
+ if list_key not in target_item:
112
+ target_item[list_key] = []
113
+ target_item[count_key] = 0
114
+
115
+ user_list = target_item[list_key]
116
+
117
+ # 执行 Toggle 逻辑
118
+ if interaction.is_active:
119
+ if interaction.user_id not in user_list:
120
+ user_list.append(interaction.user_id)
121
+ target_item[count_key] += 1
122
+ else:
123
+ if interaction.user_id in user_list:
124
+ user_list.remove(interaction.user_id)
125
+ target_item[count_key] = max(0, target_item[count_key] - 1)
126
+
127
+ db.save_data("items.json", items_db)
128
+
129
+ return {
130
+ "status": "success",
131
+ "message": f"操作成功",
132
+ "new_count": target_item[count_key]
133
+ }
134
+
135
+ # --- 4. 评论系统 ---
136
+ @app.post("/api/comments")
137
+ async def post_comment(comment: CommentCreate):
138
+ comments_db = db.load_data("comments.json", default_data={}) # 结构: {"item_id": [comment_list]}
139
+
140
+ item_comments = comments_db.get(comment.item_id, [])
141
+
142
+ new_comment = {
143
+ "id": f"c_{int(time.time())}_{uuid.uuid4().hex[:6]}",
144
+ "author": comment.author,
145
+ "content": comment.content,
146
+ "replyToUser": comment.reply_to_user,
147
+ "isDeleted": False,
148
+ "replies": [],
149
+ "created_at": int(time.time())
150
+ }
151
+
152
+ # 如果传了 parent_id,说明是子回复,要把它塞进父评论的 replies 数组里
153
+ if comment.parent_id:
154
+ parent = next((c for c in item_comments if c["id"] == comment.parent_id), None)
155
+ if parent:
156
+ parent["replies"].append(new_comment)
157
+ else:
158
+ raise HTTPException(status_code=404, detail="找不到要回复的父级评论")
159
+ else:
160
+ # 否则作为顶级评论插入
161
+ item_comments.append(new_comment)
162
+
163
+ comments_db[comment.item_id] = item_comments
164
+ db.save_data("comments.json", comments_db)
165
+
166
+ return {"status": "success", "data": new_comment}
167
+
168
+ @app.delete("/api/comments/{item_id}/{comment_id}")
169
+ async def soft_delete_comment(item_id: str, comment_id: str, author: str):
170
+ comments_db = db.load_data("comments.json", default_data={})
171
+ item_comments = comments_db.get(item_id, [])
172
+
173
+ # 递归查找要删除的评论
174
+ def find_and_delete(comments_list):
175
+ for c in comments_list:
176
+ if c["id"] == comment_id:
177
+ if c["author"] != author:
178
+ raise HTTPException(status_code=403, detail="无权删除他人的评论")
179
+ c["isDeleted"] = True
180
+ c["content"] = "" # 清空敏感或不良内容
181
+ return True
182
+ # 检查子回复
183
+ if "replies" in c and find_and_delete(c["replies"]):
184
+ return True
185
+ return False
186
+
187
+ if find_and_delete(item_comments):
188
+ db.save_data("comments.json", comments_db)
189
+ return {"status": "success", "message": "评论已软删除"}
190
+ else:
191
+ raise HTTPException(status_code=404, detail="找不到该评论")
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
+ huggingface_hub
5
+ datasets
数据库连接.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ⚙️ 后端逻辑/数据库连接.py
2
+ import os
3
+ import json
4
+ from huggingface_hub import HfApi, hf_hub_download
5
+
6
+ HF_TOKEN = os.environ.get("HF_TOKEN")
7
+ # 已经替换为你真实的 Dataset 仓库
8
+ DATASET_REPO_ID = "ZHIWEI666/ComfyUI-Ranking"
9
+ # HF Spaces 的 Docker 环境只有 /tmp 目录允许自由读写
10
+ LOCAL_DB_DIR = "/tmp/local_db_data"
11
+
12
+ api = HfApi() if HF_TOKEN else None
13
+
14
+ # 确保本地测试目录存在
15
+ if not os.path.exists(LOCAL_DB_DIR):
16
+ os.makedirs(LOCAL_DB_DIR)
17
+
18
+ def load_data(file_name: str, default_data=None):
19
+ """读取数据:优先从 HF Dataset 读取,如果没有 Token 则从本地读取"""
20
+ if default_data is None:
21
+ default_data = {} if file_name == "users.json" else []
22
+
23
+ try:
24
+ if HF_TOKEN:
25
+ file_path = hf_hub_download(repo_id=DATASET_REPO_ID, filename=file_name, repo_type="dataset", token=HF_TOKEN)
26
+ else:
27
+ file_path = os.path.join(LOCAL_DB_DIR, file_name)
28
+ if not os.path.exists(file_path):
29
+ return default_data
30
+
31
+ with open(file_path, "r", encoding="utf-8") as f:
32
+ return json.load(f)
33
+ except Exception as e:
34
+ print(f"[{file_name}] 数据拉取失败或文件不存在,使用默认空数据。原因: {e}")
35
+ return default_data
36
+
37
+ def save_data(file_name: str, data):
38
+ """保存数据:写入本地并同步到 HF Dataset"""
39
+ local_path = os.path.join(LOCAL_DB_DIR, file_name)
40
+
41
+ # 1. 始终先写入本地临时目录
42
+ with open(local_path, "w", encoding="utf-8") as f:
43
+ json.dump(data, f, ensure_ascii=False, indent=2)
44
+
45
+ # 2. 如果有 Token,同步推送到 Hugging Face
46
+ if HF_TOKEN:
47
+ try:
48
+ api.upload_file(
49
+ path_or_fileobj=local_path,
50
+ path_in_repo=file_name,
51
+ repo_id=DATASET_REPO_ID,
52
+ repo_type="dataset",
53
+ token=HF_TOKEN,
54
+ commit_message=f"Auto-update {file_name}"
55
+ )
56
+ except Exception as e:
57
+ print(f"同步到 HF Dataset 失败: {e}")