Spaces:
Running
Running
优化、排除bug
Browse files- notifications.py +30 -26
- router_items.py +112 -79
notifications.py
CHANGED
|
@@ -3,29 +3,33 @@ import uuid
|
|
| 3 |
import 数据库连接 as db
|
| 4 |
|
| 5 |
def add_notification(target_account: str, notif_data: dict):
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import 数据库连接 as db
|
| 4 |
|
| 5 |
def add_notification(target_account: str, notif_data: dict):
|
| 6 |
+
try:
|
| 7 |
+
from_user = notif_data.get("from_user")
|
| 8 |
+
if target_account == from_user or not target_account:
|
| 9 |
+
return # 不给自己发通知
|
| 10 |
+
|
| 11 |
+
users_db = db.load_data("users.json", default_data={})
|
| 12 |
+
user_info = users_db.get(from_user, {})
|
| 13 |
+
|
| 14 |
+
notif = {
|
| 15 |
+
"id": f"msg_{int(time.time())}_{uuid.uuid4().hex[:6]}",
|
| 16 |
+
"type": notif_data.get("type"),
|
| 17 |
+
"from_user": from_user,
|
| 18 |
+
"from_name": user_info.get("name", from_user),
|
| 19 |
+
"from_avatar": user_info.get("avatarDataUrl", ""),
|
| 20 |
+
"target_item_id": notif_data.get("target_item_id", ""),
|
| 21 |
+
"target_item_title": notif_data.get("target_item_title", ""),
|
| 22 |
+
"content": notif_data.get("content", ""),
|
| 23 |
+
"is_read": False,
|
| 24 |
+
"created_at": int(time.time())
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
def updater(data):
|
| 28 |
+
if target_account not in data:
|
| 29 |
+
data[target_account] = []
|
| 30 |
+
data[target_account].insert(0, notif)
|
| 31 |
+
|
| 32 |
+
db.atomic_update("messages.json", updater, default_data={})
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"⚠️ 通知发送失败 (target={target_account}): {e}")
|
| 35 |
+
# 不抛出异常,让主流程继续
|
router_items.py
CHANGED
|
@@ -175,66 +175,84 @@ async def update_item(item_id: str, update_data: ItemUpdate, current_user: str =
|
|
| 175 |
更新内容接口
|
| 176 |
🔒 P0安全修复:使用 JWT Token 验证用户身份,而非前端传入的 author 参数
|
| 177 |
🔄 P7后悔模式:价格修改延迟24小时生效
|
|
|
|
| 178 |
"""
|
| 179 |
if update_data.price is not None:
|
| 180 |
update_data.price = int(update_data.price)
|
| 181 |
if update_data.price < 0:
|
| 182 |
raise HTTPException(status_code=400, detail="🚨 安全拦截:商品价格不能为负数")
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
if item
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
if update_data.title is not None: item["title"] = update_data.title
|
| 194 |
-
if update_data.shortDesc is not None: item["shortDesc"] = update_data.shortDesc
|
| 195 |
-
if update_data.fullDesc is not None: item["fullDesc"] = update_data.fullDesc
|
| 196 |
-
if update_data.link is not None: item["link"] = update_data.link
|
| 197 |
-
if update_data.coverUrl is not None: item["coverUrl"] = update_data.coverUrl
|
| 198 |
-
if update_data.imageUrls is not None: item["imageUrls"] = update_data.imageUrls # 🖼️ 效果展示图列表
|
| 199 |
-
|
| 200 |
-
# 🔄 P7后悔模式:价格修改延迟24小时生效
|
| 201 |
-
if update_data.price is not None:
|
| 202 |
-
current_price = item.get("price", 0)
|
| 203 |
-
new_price = update_data.price
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
# ==========================================
|
| 240 |
# 🚀 定时任务接口:检查 GitHub 仓库最新版本
|
|
@@ -316,34 +334,49 @@ async def get_item_version(item_id: str):
|
|
| 316 |
async def delete_item(item_id: str, current_user: str = Depends(require_auth)):
|
| 317 |
"""
|
| 318 |
删除内容(仅作者或管理员可操作)
|
|
|
|
| 319 |
"""
|
| 320 |
-
|
| 321 |
-
comments_db = db.load_data("comments.json", default_data={})
|
| 322 |
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
-
|
| 347 |
|
| 348 |
|
| 349 |
@router.post("/api/items/{item_id}/view")
|
|
|
|
| 175 |
更新内容接口
|
| 176 |
🔒 P0安全修复:使用 JWT Token 验证用户身份,而非前端传入的 author 参数
|
| 177 |
🔄 P7后悔模式:价格修改延迟24小时生效
|
| 178 |
+
🔧 Bug修复:使用 atomic_update 避免并发竞态条件
|
| 179 |
"""
|
| 180 |
if update_data.price is not None:
|
| 181 |
update_data.price = int(update_data.price)
|
| 182 |
if update_data.price < 0:
|
| 183 |
raise HTTPException(status_code=400, detail="🚨 安全拦截:商品价格不能为负数")
|
| 184 |
+
|
| 185 |
+
result_holder = {}
|
| 186 |
+
|
| 187 |
+
def updater(items_db):
|
| 188 |
+
for item in items_db:
|
| 189 |
+
if item["id"] == item_id:
|
| 190 |
+
# 🔒 P0安全修复:使用 JWT 解析出的真实用户账号进行校验
|
| 191 |
+
if item.get("author") != current_user:
|
| 192 |
+
result_holder["error"] = "forbidden"
|
| 193 |
+
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
+
price_change_info = None
|
| 196 |
+
|
| 197 |
+
if update_data.title is not None: item["title"] = update_data.title
|
| 198 |
+
if update_data.shortDesc is not None: item["shortDesc"] = update_data.shortDesc
|
| 199 |
+
if update_data.fullDesc is not None: item["fullDesc"] = update_data.fullDesc
|
| 200 |
+
if update_data.link is not None: item["link"] = update_data.link
|
| 201 |
+
if update_data.coverUrl is not None: item["coverUrl"] = update_data.coverUrl
|
| 202 |
+
if update_data.imageUrls is not None: item["imageUrls"] = update_data.imageUrls # 🖼️ 效果展示图列表
|
| 203 |
+
|
| 204 |
+
# 🔄 P7后悔模式:价格修改延迟24小时生效
|
| 205 |
+
if update_data.price is not None:
|
| 206 |
+
current_price = item.get("price", 0)
|
| 207 |
+
new_price = update_data.price
|
| 208 |
+
|
| 209 |
+
if current_price != new_price:
|
| 210 |
+
# 设置待生效价格,24小时后生效
|
| 211 |
+
import datetime
|
| 212 |
+
effective_time = datetime.datetime.now() + datetime.timedelta(hours=24)
|
| 213 |
+
item["pending_price"] = new_price
|
| 214 |
+
item["pending_price_effective_at"] = effective_time.isoformat()
|
| 215 |
+
price_change_info = {
|
| 216 |
+
"current_price": current_price,
|
| 217 |
+
"new_price": new_price,
|
| 218 |
+
"effective_at": effective_time.isoformat()
|
| 219 |
+
}
|
| 220 |
+
# 不立即修改 price,等待24小时后生效
|
| 221 |
+
else:
|
| 222 |
+
# 价格未变,清除待生效价格
|
| 223 |
+
item["pending_price"] = None
|
| 224 |
+
item["pending_price_effective_at"] = None
|
| 225 |
+
|
| 226 |
+
if update_data.github_token is not None: item["github_token"] = update_data.github_token
|
| 227 |
+
if update_data.netdisk_password is not None: item["netdisk_password"] = update_data.netdisk_password # ☁️
|
| 228 |
+
if update_data.is_netdisk is not None: item["is_netdisk"] = update_data.is_netdisk # ☁️
|
| 229 |
+
if update_data.is_original is not None: item["is_original"] = update_data.is_original # 🎨
|
| 230 |
+
|
| 231 |
+
result_holder["success"] = True
|
| 232 |
+
result_holder["price_change_info"] = price_change_info
|
| 233 |
+
result_holder["current_price"] = price_change_info.get("current_price") if price_change_info else None
|
| 234 |
+
result_holder["new_price"] = price_change_info.get("new_price") if price_change_info else None
|
| 235 |
+
return True
|
| 236 |
+
|
| 237 |
+
result_holder["error"] = "not_found"
|
| 238 |
+
return False
|
| 239 |
+
|
| 240 |
+
db.atomic_update("items.json", updater, default_data=[])
|
| 241 |
+
|
| 242 |
+
if result_holder.get("error") == "forbidden":
|
| 243 |
+
raise HTTPException(status_code=403, detail="无权修改他人发布的内容")
|
| 244 |
+
if result_holder.get("error") == "not_found":
|
| 245 |
+
raise HTTPException(status_code=404, detail="找不到该内容记录")
|
| 246 |
+
|
| 247 |
+
# 🗂️ 清除排序缓存
|
| 248 |
+
sort_cache.invalidate("items:")
|
| 249 |
+
|
| 250 |
+
result = {"status": "success"}
|
| 251 |
+
if result_holder.get("price_change_info"):
|
| 252 |
+
price_change_info = result_holder["price_change_info"]
|
| 253 |
+
result["price_change"] = price_change_info
|
| 254 |
+
result["message"] = f"价格将于24小时后从{price_change_info['current_price']}调整为{price_change_info['new_price']}积分"
|
| 255 |
+
return result
|
| 256 |
|
| 257 |
# ==========================================
|
| 258 |
# 🚀 定时任务接口:检查 GitHub 仓库最新版本
|
|
|
|
| 334 |
async def delete_item(item_id: str, current_user: str = Depends(require_auth)):
|
| 335 |
"""
|
| 336 |
删除内容(仅作者或管理员可操作)
|
| 337 |
+
🔧 Bug修复:使用 atomic_update 避免并发竞态条件
|
| 338 |
"""
|
| 339 |
+
result_holder = {}
|
|
|
|
| 340 |
|
| 341 |
+
def items_updater(items_db):
|
| 342 |
+
for i, item in enumerate(items_db):
|
| 343 |
+
if item["id"] == item_id:
|
| 344 |
+
# 🔒 权限检查:仅作者或管理员可删除
|
| 345 |
+
if not check_ownership(item, current_user, owner_field="author", allow_admin=True):
|
| 346 |
+
result_holder["error"] = "forbidden"
|
| 347 |
+
return False
|
| 348 |
+
|
| 349 |
+
# 1. 从 items.json 中删除该条目
|
| 350 |
+
items_db.pop(i)
|
| 351 |
+
result_holder["item_deleted"] = True
|
| 352 |
+
return True
|
| 353 |
+
|
| 354 |
+
result_holder["error"] = "not_found"
|
| 355 |
+
return False
|
| 356 |
+
|
| 357 |
+
db.atomic_update("items.json", items_updater, default_data=[])
|
| 358 |
+
|
| 359 |
+
if result_holder.get("error") == "forbidden":
|
| 360 |
+
raise HTTPException(status_code=403, detail="无权删除他人发布的内容")
|
| 361 |
+
if result_holder.get("error") == "not_found":
|
| 362 |
+
raise HTTPException(status_code=404, detail="找不到该内容记录")
|
| 363 |
+
|
| 364 |
+
# 2. 清理关联评论:从 comments.json 中删除该内容的所有评论
|
| 365 |
+
def comments_updater(comments_db):
|
| 366 |
+
if item_id in comments_db:
|
| 367 |
+
del comments_db[item_id]
|
| 368 |
+
return True
|
| 369 |
+
return False
|
| 370 |
+
|
| 371 |
+
db.atomic_update("comments.json", comments_updater, default_data={})
|
| 372 |
+
|
| 373 |
+
# 3. 清理缓存:使 items.json 和 comments.json 的缓存失效
|
| 374 |
+
invalidate_cache("items.json")
|
| 375 |
+
invalidate_cache("comments.json")
|
| 376 |
+
# 🗂️ 清除排序缓存
|
| 377 |
+
sort_cache.invalidate("items:")
|
| 378 |
|
| 379 |
+
return {"status": "success", "message": "内容已删除"}
|
| 380 |
|
| 381 |
|
| 382 |
@router.post("/api/items/{item_id}/view")
|