ZHIWEI666 commited on
Commit
856d1b0
·
verified ·
1 Parent(s): c64fa2c

增加打分5星机制

Browse files
Files changed (3) hide show
  1. models.py +12 -1
  2. router_items.py +82 -2
  3. router_posts.py +86 -10
models.py CHANGED
@@ -1,5 +1,5 @@
1
  # models.py
2
- from pydantic import BaseModel
3
  from typing import Optional, List
4
 
5
  class SendCodeRequest(BaseModel):
@@ -105,6 +105,17 @@ class InteractionToggle(BaseModel):
105
  action_type: str
106
  is_active: bool
107
 
 
 
 
 
 
 
 
 
 
 
 
108
  class RechargeRequest(BaseModel):
109
  account: str
110
  amount: int
 
1
  # models.py
2
+ from pydantic import BaseModel, validator
3
  from typing import Optional, List
4
 
5
  class SendCodeRequest(BaseModel):
 
105
  action_type: str
106
  is_active: bool
107
 
108
+ class RatingRequest(BaseModel):
109
+ score: int # 1-5
110
+
111
+ @validator("score")
112
+ def validate_score(cls, v):
113
+ if not isinstance(v, int):
114
+ raise ValueError("score must be an integer between 1 and 5")
115
+ if v < 1 or v > 5:
116
+ raise ValueError("score must be between 1 and 5")
117
+ return v
118
+
119
  class RechargeRequest(BaseModel):
120
  account: str
121
  amount: int
router_items.py CHANGED
@@ -8,7 +8,7 @@ import urllib.request
8
  import urllib.error
9
  import json
10
  import 数据库连接 as db
11
- from models import ItemCreate, ItemUpdate
12
  from 安全认证 import require_auth, check_ownership
13
  from 数据库连接 import invalidate_cache
14
  from db_utils import record_view, sort_cache
@@ -59,6 +59,8 @@ async def get_items(type: str = "tool", sort: str = "time", limit: int = 50): #
59
  data.sort(key=lambda x: x.get("views", 0), reverse=True)
60
  elif sort == "daily_views": # 👁️ 按日访问量排序
61
  data.sort(key=lambda x: x.get("daily_views", 0), reverse=True)
 
 
62
  else: # time 或其他默认
63
  data.sort(key=lambda x: x.get("created_at", 0), reverse=True)
64
 
@@ -293,7 +295,11 @@ async def create_item(item: ItemCreate):
293
  "views": 0,
294
  "daily_views": 0,
295
  "viewed_by": [],
296
- "daily_views_date": ""
 
 
 
 
297
  }
298
  items_db.insert(0, new_item)
299
  db.save_data("items.json", items_db)
@@ -598,6 +604,80 @@ async def record_item_use(item_id: str, current_user: str = Depends(require_auth
598
  # ❤️ 互动接口(点赞/收藏)
599
  # ==========================================
600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  @router.post("/api/items/{item_id}/like")
602
  async def toggle_item_like(item_id: str, current_user: str = Depends(require_auth)):
603
  """
 
8
  import urllib.error
9
  import json
10
  import 数据库连接 as db
11
+ from models import ItemCreate, ItemUpdate, RatingRequest
12
  from 安全认证 import require_auth, check_ownership
13
  from 数据库连接 import invalidate_cache
14
  from db_utils import record_view, sort_cache
 
59
  data.sort(key=lambda x: x.get("views", 0), reverse=True)
60
  elif sort == "daily_views": # 👁️ 按日访问量排序
61
  data.sort(key=lambda x: x.get("daily_views", 0), reverse=True)
62
+ elif sort == "rating": # ⭐ 按评分排序
63
+ data.sort(key=lambda x: (x.get("rating_avg", 0), x.get("rating_count", 0)), reverse=True)
64
  else: # time 或其他默认
65
  data.sort(key=lambda x: x.get("created_at", 0), reverse=True)
66
 
 
295
  "views": 0,
296
  "daily_views": 0,
297
  "viewed_by": [],
298
+ "daily_views_date": "",
299
+ "rating_avg": 0.0,
300
+ "rating_count": 0,
301
+ "rating_dist": {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0},
302
+ "rated_by": {}
303
  }
304
  items_db.insert(0, new_item)
305
  db.save_data("items.json", items_db)
 
604
  # ❤️ 互动接口(点赞/收藏)
605
  # ==========================================
606
 
607
+ @router.post("/api/items/{item_id}/rating")
608
+ async def rate_item(item_id: str, request: RatingRequest, current_user: str = Depends(require_auth)):
609
+ """
610
+ 为资源评分(原子操作,并发安全)
611
+ ⭐ score: 1-5
612
+ """
613
+ score = request.score
614
+ if score < 1 or score > 5:
615
+ raise HTTPException(status_code=400, detail="评分必须在1-5之间")
616
+
617
+ result_container = [None]
618
+
619
+ def updater(data):
620
+ for item in data:
621
+ if item["id"] == item_id:
622
+ # 禁止自评
623
+ if item.get("author") == current_user:
624
+ result_container[0] = {"error": "self_rating"}
625
+ return
626
+ # 初始化评分字段
627
+ if "rating_avg" not in item:
628
+ item["rating_avg"] = 0.0
629
+ if "rating_count" not in item:
630
+ item["rating_count"] = 0
631
+ if "rating_dist" not in item:
632
+ item["rating_dist"] = {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0}
633
+ if "rated_by" not in item:
634
+ item["rated_by"] = {}
635
+
636
+ rated_by = item["rated_by"]
637
+ rating_dist = item["rating_dist"]
638
+ old_score = None
639
+ if current_user in rated_by:
640
+ old_score = rated_by[current_user]["score"]
641
+
642
+ if old_score is not None:
643
+ # 已评分,先减去旧分数分布
644
+ rating_dist[str(old_score)] = max(0, rating_dist.get(str(old_score), 0) - 1)
645
+ rating_dist[str(score)] = rating_dist.get(str(score), 0) + 1
646
+ else:
647
+ # 未评分,增加计数
648
+ item["rating_count"] = item.get("rating_count", 0) + 1
649
+ rating_dist[str(score)] = rating_dist.get(str(score), 0) + 1
650
+
651
+ rated_by[current_user] = {"score": score, "time": int(time.time())}
652
+
653
+ # 重新计算平均分
654
+ total = sum(int(k) * v for k, v in rating_dist.items())
655
+ count = item["rating_count"]
656
+ item["rating_avg"] = round(total / count, 2) if count > 0 else 0.0
657
+
658
+ result_container[0] = {
659
+ "status": "success",
660
+ "rating_avg": item["rating_avg"],
661
+ "rating_count": item["rating_count"],
662
+ "rating_dist": item["rating_dist"],
663
+ "user_score": score
664
+ }
665
+ return
666
+ result_container[0] = None # 未找到资源
667
+
668
+ db.atomic_update("items.json", updater, default_data=[])
669
+
670
+ if result_container[0] is None:
671
+ raise HTTPException(status_code=404, detail="资源不存在")
672
+ if result_container[0].get("error") == "self_rating":
673
+ raise HTTPException(status_code=400, detail="不能给自己发布的资源评分")
674
+
675
+ # 🗂️ 清除排序缓存(评分变化可能影响排序)
676
+ sort_cache.invalidate("items:")
677
+
678
+ return result_container[0]
679
+
680
+
681
  @router.post("/api/items/{item_id}/like")
682
  async def toggle_item_like(item_id: str, current_user: str = Depends(require_auth)):
683
  """
router_posts.py CHANGED
@@ -7,7 +7,7 @@
7
 
8
  from fastapi import APIRouter, HTTPException, Depends
9
  from sqlalchemy.orm import Session
10
- from models import PostCreate, PostUpdate
11
  import 数据库连接 as db
12
  from 安全认证 import require_auth
13
  from db_utils import record_view, sort_cache
@@ -57,6 +57,8 @@ async def get_posts(page: int = 1, limit: int = 20, sort: str = "latest"):
57
  data.sort(key=lambda x: x.get("daily_views", 0), reverse=True)
58
  elif sort == "tips":
59
  data.sort(key=lambda x: sum(t.get("amount", 0) for t in x.get("tip_board", [])), reverse=True)
 
 
60
  else: # latest 或其他默认
61
  data.sort(key=lambda x: x.get("created_at", 0), reverse=True)
62
 
@@ -76,12 +78,10 @@ async def get_posts(page: int = 1, limit: int = 20, sort: str = "latest"):
76
  "author_name": author_info.get("name", post.get("author")),
77
  "author_avatar": author_info.get("avatarDataUrl", "")
78
  }
79
- # 过滤敏感字段(列表接口过滤 viewed_byliked_byfavorited_by)
80
  post_data.pop("viewed_by", None)
81
- post_data.pop("liked_by", None)
82
- post_data.pop("favorited_by", None)
83
  result.append(post_data)
84
-
85
  return {
86
  "status": "success",
87
  "data": result,
@@ -103,15 +103,13 @@ async def get_my_posts(current_user: str = Depends(require_auth)):
103
  # 按创建时间倒序
104
  my_posts = sorted(my_posts, key=lambda x: x.get("created_at", 0), reverse=True)
105
 
106
- # 过滤敏感字段(列表接口过滤 viewed_byliked_byfavorited_by)
107
  result = []
108
  for post in my_posts:
109
  post_data = dict(post)
110
  post_data.pop("viewed_by", None)
111
- post_data.pop("liked_by", None)
112
- post_data.pop("favorited_by", None)
113
  result.append(post_data)
114
-
115
  return {
116
  "status": "success",
117
  "data": result
@@ -174,7 +172,11 @@ async def create_post(post: PostCreate, current_user: str = Depends(require_auth
174
  "views": 0,
175
  "daily_views": 0,
176
  "viewed_by": [],
177
- "daily_views_date": ""
 
 
 
 
178
  }
179
 
180
  posts_db.insert(0, new_post)
@@ -248,6 +250,80 @@ async def delete_post(post_id: str, current_user: str = Depends(require_auth)):
248
  # ❤️ 互动接口(点赞/收藏)
249
  # ==========================================
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  @router.post("/api/posts/{post_id}/like")
252
  async def toggle_like(post_id: str, current_user: str = Depends(require_auth)):
253
  """
 
7
 
8
  from fastapi import APIRouter, HTTPException, Depends
9
  from sqlalchemy.orm import Session
10
+ from models import PostCreate, PostUpdate, RatingRequest
11
  import 数据库连接 as db
12
  from 安全认证 import require_auth
13
  from db_utils import record_view, sort_cache
 
57
  data.sort(key=lambda x: x.get("daily_views", 0), reverse=True)
58
  elif sort == "tips":
59
  data.sort(key=lambda x: sum(t.get("amount", 0) for t in x.get("tip_board", [])), reverse=True)
60
+ elif sort == "rating": # ⭐ 按评分排序
61
+ data.sort(key=lambda x: (x.get("rating_avg", 0), x.get("rating_count", 0)), reverse=True)
62
  else: # latest 或其他默认
63
  data.sort(key=lambda x: x.get("created_at", 0), reverse=True)
64
 
 
78
  "author_name": author_info.get("name", post.get("author")),
79
  "author_avatar": author_info.get("avatarDataUrl", "")
80
  }
81
+ # 过滤敏感字段(列表接口过滤 viewed_by,保留 liked_byfavorited_by)
82
  post_data.pop("viewed_by", None)
 
 
83
  result.append(post_data)
84
+
85
  return {
86
  "status": "success",
87
  "data": result,
 
103
  # 按创建时间倒序
104
  my_posts = sorted(my_posts, key=lambda x: x.get("created_at", 0), reverse=True)
105
 
106
+ # 过滤敏感字段(列表接口过滤 viewed_by,保留 liked_byfavorited_by)
107
  result = []
108
  for post in my_posts:
109
  post_data = dict(post)
110
  post_data.pop("viewed_by", None)
 
 
111
  result.append(post_data)
112
+
113
  return {
114
  "status": "success",
115
  "data": result
 
172
  "views": 0,
173
  "daily_views": 0,
174
  "viewed_by": [],
175
+ "daily_views_date": "",
176
+ "rating_avg": 0.0,
177
+ "rating_count": 0,
178
+ "rating_dist": {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0},
179
+ "rated_by": {}
180
  }
181
 
182
  posts_db.insert(0, new_post)
 
250
  # ❤️ 互动接口(点赞/收藏)
251
  # ==========================================
252
 
253
+ @router.post("/api/posts/{post_id}/rating")
254
+ async def rate_post(post_id: str, request: RatingRequest, current_user: str = Depends(require_auth)):
255
+ """
256
+ 为帖子评分(原子操作,并发安全)
257
+ ⭐ score: 1-5
258
+ """
259
+ score = request.score
260
+ if score < 1 or score > 5:
261
+ raise HTTPException(status_code=400, detail="评分必须在1-5之间")
262
+
263
+ result_container = [None]
264
+
265
+ def updater(data):
266
+ for post in data:
267
+ if post["id"] == post_id:
268
+ # 禁止自评
269
+ if post.get("author") == current_user:
270
+ result_container[0] = {"error": "self_rating"}
271
+ return
272
+ # 初始化评分字段
273
+ if "rating_avg" not in post:
274
+ post["rating_avg"] = 0.0
275
+ if "rating_count" not in post:
276
+ post["rating_count"] = 0
277
+ if "rating_dist" not in post:
278
+ post["rating_dist"] = {"1": 0, "2": 0, "3": 0, "4": 0, "5": 0}
279
+ if "rated_by" not in post:
280
+ post["rated_by"] = {}
281
+
282
+ rated_by = post["rated_by"]
283
+ rating_dist = post["rating_dist"]
284
+ old_score = None
285
+ if current_user in rated_by:
286
+ old_score = rated_by[current_user]["score"]
287
+
288
+ if old_score is not None:
289
+ # 已评分,先减去旧分数分布
290
+ rating_dist[str(old_score)] = max(0, rating_dist.get(str(old_score), 0) - 1)
291
+ rating_dist[str(score)] = rating_dist.get(str(score), 0) + 1
292
+ else:
293
+ # 未评分,增加计数
294
+ post["rating_count"] = post.get("rating_count", 0) + 1
295
+ rating_dist[str(score)] = rating_dist.get(str(score), 0) + 1
296
+
297
+ rated_by[current_user] = {"score": score, "time": int(time.time())}
298
+
299
+ # 重新计算平均��
300
+ total = sum(int(k) * v for k, v in rating_dist.items())
301
+ count = post["rating_count"]
302
+ post["rating_avg"] = round(total / count, 2) if count > 0 else 0.0
303
+
304
+ result_container[0] = {
305
+ "status": "success",
306
+ "rating_avg": post["rating_avg"],
307
+ "rating_count": post["rating_count"],
308
+ "rating_dist": post["rating_dist"],
309
+ "user_score": score
310
+ }
311
+ return
312
+ result_container[0] = None # 未找到帖子
313
+
314
+ db.atomic_update("posts.json", updater, default_data=[])
315
+
316
+ if result_container[0] is None:
317
+ raise HTTPException(status_code=404, detail="帖子不存在")
318
+ if result_container[0].get("error") == "self_rating":
319
+ raise HTTPException(status_code=400, detail="不能给自己发布的帖子评分")
320
+
321
+ # 🗂️ 清除排序缓存(评分变化可能影响排序)
322
+ sort_cache.invalidate("posts:")
323
+
324
+ return result_container[0]
325
+
326
+
327
  @router.post("/api/posts/{post_id}/like")
328
  async def toggle_like(post_id: str, current_user: str = Depends(require_auth)):
329
  """