修复令牌验证逻辑,修改管理员dashboard,增加退出登录功能
Change-Id: I6a832763126dffd28733269044a1b1956c5b1106
diff --git a/Merge/back_rhj/__pycache__/config.cpython-310.pyc b/Merge/back_rhj/__pycache__/config.cpython-310.pyc
new file mode 100644
index 0000000..0d95d54
--- /dev/null
+++ b/Merge/back_rhj/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc b/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..bc775fd
--- /dev/null
+++ b/Merge/back_rhj/app/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc b/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc
new file mode 100644
index 0000000..f5aa9b3
--- /dev/null
+++ b/Merge/back_rhj/app/__pycache__/routes.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-310.pyc b/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-310.pyc
new file mode 100644
index 0000000..caf8ad5
--- /dev/null
+++ b/Merge/back_rhj/app/blueprints/__pycache__/recommend.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/blueprints/__pycache__/scheduler.cpython-310.pyc b/Merge/back_rhj/app/blueprints/__pycache__/scheduler.cpython-310.pyc
new file mode 100644
index 0000000..44681a7
--- /dev/null
+++ b/Merge/back_rhj/app/blueprints/__pycache__/scheduler.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-310.pyc b/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-310.pyc
new file mode 100644
index 0000000..f4aa5b2
--- /dev/null
+++ b/Merge/back_rhj/app/functions/__pycache__/FAuth.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/__init__.cpython-310.pyc b/Merge/back_rhj/app/models/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..2434dcb
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-310.pyc b/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-310.pyc
new file mode 100644
index 0000000..39d1bfa
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/email_verification.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/__pycache__/users.cpython-310.pyc b/Merge/back_rhj/app/models/__pycache__/users.cpython-310.pyc
new file mode 100644
index 0000000..33b799e
--- /dev/null
+++ b/Merge/back_rhj/app/models/__pycache__/users.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..d6c32d0
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-310.pyc
new file mode 100644
index 0000000..92342d0
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/ad_recall.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-310.pyc
new file mode 100644
index 0000000..af23bff
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/hot_recall.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-310.pyc
new file mode 100644
index 0000000..59158dc
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/multi_recall_manager.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc
new file mode 100644
index 0000000..5cd2aee
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/swing_recall.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-310.pyc b/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-310.pyc
new file mode 100644
index 0000000..25df6ea
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/__pycache__/usercf_recall.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recall/ad_recall.py b/Merge/back_rhj/app/models/recall/ad_recall.py
new file mode 100644
index 0000000..cc2c602
--- /dev/null
+++ b/Merge/back_rhj/app/models/recall/ad_recall.py
@@ -0,0 +1,206 @@
+import pymysql
+from typing import List, Tuple, Dict
+import random
+class AdRecall:
+ """
+ 广告召回算法实现
+ 专门用于召回广告类型的内容
+ """
+
+ def __init__(self, db_config: dict):
+ """
+ 初始化广告召回模型
+
+ Args:
+ db_config: 数据库配置
+ """
+ self.db_config = db_config
+ self.ad_items = []
+
+ def _get_ad_items(self):
+ """获取广告物品列表"""
+ conn = pymysql.connect(**self.db_config)
+ try:
+ cursor = conn.cursor()
+
+ # 获取所有广告帖子,按热度和发布时间排序
+ cursor.execute("""
+ SELECT
+ p.id,
+ p.heat,
+ p.created_at,
+ COUNT(DISTINCT b.user_id) as interaction_count,
+ DATEDIFF(NOW(), p.created_at) as days_since_created
+ FROM posts p
+ LEFT JOIN behaviors b ON p.id = b.post_id
+ WHERE p.is_advertisement = 1 AND p.status = 'published'
+ GROUP BY p.id, p.heat, p.created_at
+ ORDER BY p.heat DESC, p.created_at DESC
+ """)
+
+ results = cursor.fetchall()
+
+ # 计算广告分数
+ items_with_scores = []
+ for row in results:
+ post_id, heat, created_at, interaction_count, days_since_created = row
+
+ # 处理 None 值
+ heat = heat or 0
+ interaction_count = interaction_count or 0
+ days_since_created = days_since_created or 0
+
+ # 广告分数计算:热度 + 交互数 - 时间惩罚
+ # 新发布的广告给予更高权重
+ freshness_bonus = max(0, 30 - days_since_created) / 30.0 # 30 天内的新鲜度奖励
+
+ ad_score = (
+ heat * 0.6 +
+ interaction_count * 0.3 +
+ freshness_bonus * 100 # 新鲜度奖励
+ )
+
+ items_with_scores.append((post_id, ad_score))
+
+ # 按广告分数排序
+ self.ad_items = sorted(items_with_scores, key=lambda x: x[1], reverse=True)
+
+ finally:
+ cursor.close()
+ conn.close()
+
+ def train(self):
+ """训练广告召回模型"""
+ print("开始获取广告物品...")
+ self._get_ad_items()
+ print(f"广告召回模型训练完成,共 {len (self.ad_items)} 个广告物品")
+
+ def recall(self, user_id: int, num_items: int = 10) -> List[Tuple[int, float]]:
+ """
+ 为用户召回广告物品
+
+ Args:
+ user_id: 用户 ID
+ num_items: 召回物品数量
+
+ Returns:
+ List of (item_id, score) tuples
+ """
+ # 如果尚未训练,先进行训练
+ if not hasattr(self, 'ad_items') or not self.ad_items:
+ self.train()
+
+ # 获取用户已交互的广告,避免重复推荐
+ conn = pymysql.connect(**self.db_config)
+ try:
+ cursor = conn.cursor()
+ cursor.execute("""
+ SELECT DISTINCT b.post_id
+ FROM behaviors b
+ JOIN posts p ON b.post_id = p.id
+ WHERE b.user_id = %s AND p.is_advertisement = 1
+ AND b.type IN ('like', 'favorite', 'comment', 'view')
+ """, (user_id,))
+
+ user_interacted_ads = set(row[0] for row in cursor.fetchall())
+
+ # 获取用户的兴趣标签(基于历史行为)
+ cursor.execute("""
+ SELECT t.name, COUNT(*) as count
+ FROM behaviors b
+ JOIN posts p ON b.post_id = p.id
+ JOIN post_tags pt ON p.id = pt.post_id
+ JOIN tags t ON pt.tag_id = t.id
+ WHERE b.user_id = %s AND b.type IN ('like', 'favorite', 'comment')
+ GROUP BY t.name
+ ORDER BY count DESC
+ LIMIT 10
+ """, (user_id,))
+
+ user_interest_tags = set(row[0] for row in cursor.fetchall())
+
+ finally:
+ cursor.close()
+ conn.close()
+
+ # 过滤掉用户已交互的广告
+ filtered_ads = [
+ (item_id, score) for item_id, score in self.ad_items
+ if item_id not in user_interacted_ads
+ ]
+
+ # 如果没有未交互的广告,但有广告数据,返回评分最高的广告(可能用户会再次感兴趣)
+ if not filtered_ads and self.ad_items:
+ print(f"用户 {user_id} 已与所有广告交互,返回评分最高的广告")
+ filtered_ads = self.ad_items[:num_items]
+
+ # 如果用户有兴趣标签,可以进一步个性化广告推荐
+ if user_interest_tags and filtered_ads:
+ filtered_ads = self._personalize_ads(filtered_ads, user_interest_tags)
+
+ return filtered_ads[:num_items]
+
+ def _personalize_ads(self, ad_list: List[Tuple[int, float]], user_interest_tags: set) -> List[Tuple[int, float]]:
+ """
+ 根据用户兴趣标签个性化广告推荐
+
+ Args:
+ ad_list: 广告列表
+ user_interest_tags: 用户兴趣标签
+
+ Returns:
+ 个性化后的广告列表
+ """
+ conn = pymysql.connect(**self.db_config)
+ try:
+ cursor = conn.cursor()
+
+ personalized_ads = []
+ for ad_id, ad_score in ad_list:
+ # 获取广告的标签
+ cursor.execute("""
+ SELECT t.name
+ FROM post_tags pt
+ JOIN tags t ON pt.tag_id = t.id
+ WHERE pt.post_id = %s
+ """, (ad_id,))
+
+ ad_tags = set(row[0] for row in cursor.fetchall())
+
+ # 计算标签匹配度
+ tag_match_score = len(ad_tags & user_interest_tags) / max(len(user_interest_tags), 1)
+
+ # 调整广告分数
+ final_score = ad_score * (1 + tag_match_score)
+ personalized_ads.append((ad_id, final_score))
+
+ # 重新排序
+ personalized_ads.sort(key=lambda x: x[1], reverse=True)
+ return personalized_ads
+
+ finally:
+ cursor.close()
+ conn.close()
+
+ def get_random_ads(self, num_items: int = 5) -> List[Tuple[int, float]]:
+ """
+ 获取随机广告(用于多样性)
+
+ Args:
+ num_items: 返回物品数量
+
+ Returns:
+ List of (item_id, score) tuples
+ """
+ if len(self.ad_items) <= num_items:
+ return self.ad_items
+
+ # 随机选择但倾向于高分广告
+ weights = [score for _, score in self.ad_items]
+ selected_indices = random.choices(
+ range(len(self.ad_items)),
+ weights=weights,
+ k=num_items
+ )
+
+ return [self.ad_items[i] for i in selected_indices]
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-310.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-310.pyc
new file mode 100644
index 0000000..cf9b64f
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/LightGCN.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-310.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-310.pyc
new file mode 100644
index 0000000..19f10bf
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/base_model.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-310.pyc b/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-310.pyc
new file mode 100644
index 0000000..fa721af
--- /dev/null
+++ b/Merge/back_rhj/app/models/recommend/__pycache__/operators.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc
new file mode 100644
index 0000000..495528d
--- /dev/null
+++ b/Merge/back_rhj/app/services/__pycache__/lightgcn_scorer.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc
new file mode 100644
index 0000000..88ab960
--- /dev/null
+++ b/Merge/back_rhj/app/services/__pycache__/recommendation_service.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-310.pyc b/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-310.pyc
new file mode 100644
index 0000000..a145deb
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/data_loader.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-310.pyc b/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-310.pyc
new file mode 100644
index 0000000..b802281
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/graph_build.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-310.pyc b/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-310.pyc
new file mode 100644
index 0000000..548f131
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/parse_args.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_rhj/app/utils/__pycache__/scheduler_manager.cpython-310.pyc b/Merge/back_rhj/app/utils/__pycache__/scheduler_manager.cpython-310.pyc
new file mode 100644
index 0000000..a280927
--- /dev/null
+++ b/Merge/back_rhj/app/utils/__pycache__/scheduler_manager.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/__pycache__/config.cpython-310.pyc b/Merge/back_trm/__pycache__/config.cpython-310.pyc
index 47d55f3..0255d9e 100644
--- a/Merge/back_trm/__pycache__/config.cpython-310.pyc
+++ b/Merge/back_trm/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app.py b/Merge/back_trm/app.py
index af7fefc..f1a9ec1 100644
--- a/Merge/back_trm/app.py
+++ b/Merge/back_trm/app.py
@@ -10,7 +10,11 @@
from app.functions.Fpost import Fpost;
app = create_app()
-CORS(app, resources={r"/*": {"origins": "*"}})
+CORS(app,
+ resources={r"/*": {"origins": "*"}},
+ supports_credentials=True,
+ allow_headers=["Content-Type"]
+)
proc=psutil.Process(os.getpid())
@app.before_request
diff --git a/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc b/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc
index e22e52b..8eadf0e 100644
--- a/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc
+++ b/Merge/back_trm/app/__pycache__/routes.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/functions/Fpost.py b/Merge/back_trm/app/functions/Fpost.py
index 2237815..248ed13 100644
--- a/Merge/back_trm/app/functions/Fpost.py
+++ b/Merge/back_trm/app/functions/Fpost.py
@@ -6,6 +6,10 @@
from sqlalchemy.orm import Session
from ..models.logs import Log
from ..models.syscost import PerformanceData
+# from ..models.token import Token
+from config import Config
+import requests
+
class Fpost:
def __init__(self,session:Session):
self.session=session
@@ -48,13 +52,30 @@
def getpost(self,postid):
res=self.session.query(post).filter(post.id==postid).first()
return res
- def checkid(self,userid,status=''):
- res=self.session.query(users).filter(users.id==userid).first()
- if(not res):
+ def checkid(self, token, status=''):
+ """
+ 使用前端传来的 token 调用 /verify_user 接口校验,
+ 如果接口返回 success=False 或者角色不匹配则返回 False,否则 True。
+ """
+ print("---------------------------------------------------------")
+ try:
+ url = f"{Config.API_BASE_URL}/verify_user"
+ headers = {
+ 'Authorization': f'Bearer {token}',
+ 'Content-Type': 'application/json'
+ }
+ payload = {'token': token}
+ resp = requests.post(url, headers=headers, json=payload)
+
+ print(resp.json())
+ if resp.status_code != 200:
+ return False
+ data = resp.json()
+ if not data.get('success'):
+ return False
+ return data.get('role') == status,data.get('user_id')
+ except Exception:
return False
- if res.role !=status:
- return False
- return True
def review(self,postid,status):
print(status)
@@ -65,43 +86,43 @@
self.session.commit()
return True
- def createtoken(self, userid):
- """
- 根据userid创建token并插入到数据库
- :param userid: 用户ID
- :return: 生成的token字符串
- """
- # 生成随机盐值
- salt = secrets.token_hex(16)
+ # def createtoken(self, userid):
+ # """
+ # 根据userid创建token并插入到数据库
+ # :param userid: 用户ID
+ # :return: 生成的token字符串
+ # """
+ # # 生成随机盐值
+ # salt = secrets.token_hex(16)
- # 创建哈希值:userid + 当前时间戳 + 随机盐值
- current_time = str(datetime.now().timestamp())
- hash_input = f"{userid}_{current_time}_{salt}"
+ # # 创建哈希值:userid + 当前时间戳 + 随机盐值
+ # current_time = str(datetime.now().timestamp())
+ # hash_input = f"{userid}_{current_time}_{salt}"
- # 生成SHA256哈希值作为token
- token = hashlib.sha256(hash_input.encode()).hexdigest()
+ # # 生成SHA256哈希值作为token
+ # token = hashlib.sha256(hash_input.encode()).hexdigest()
- # 设置时间
- created_time = datetime.now()
- expires_time = created_time + timedelta(days=1) # 一天后过期
+ # # 设置时间
+ # created_time = datetime.now()
+ # expires_time = created_time + timedelta(days=1) # 一天后过期
- try:
- # 创建新的token记录
- new_token = Token(
- token=token,
- expires_at=expires_time,
- created_at=created_time
- )
+ # try:
+ # # 创建新的token记录
+ # new_token = Token(
+ # token=token,
+ # expires_at=expires_time,
+ # created_at=created_time
+ # )
- # 假设self.session是数据库会话对象
- self.session.add(new_token)
- self.session.commit()
+ # # 假设self.session是数据库会话对象
+ # self.session.add(new_token)
+ # self.session.commit()
- return token
+ # return token
- except Exception as e:
- self.session.rollback()
- raise Exception(f"创建token失败: {str(e)}")
+ # except Exception as e:
+ # self.session.rollback()
+ # raise Exception(f"创建token失败: {str(e)}")
def recordlog(self,user_id,log_type,content,ip):
"""
@@ -152,5 +173,5 @@
raise Exception(f"记录系统性能消耗失败: {e}")
def getsyscost(self):
- res= self.session.query(PerformanceData).all()
+ res = self.session.query(PerformanceData).order_by(PerformanceData.record_time.desc()).limit(200).all()
return res
\ No newline at end of file
diff --git a/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc b/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
index 3a49c6c..88279b4 100644
--- a/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
+++ b/Merge/back_trm/app/functions/__pycache__/Fpost.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/models/__pycache__/token.cpython-310.pyc b/Merge/back_trm/app/models/__pycache__/token.cpython-310.pyc
new file mode 100644
index 0000000..ab59118
--- /dev/null
+++ b/Merge/back_trm/app/models/__pycache__/token.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_trm/app/routes.py b/Merge/back_trm/app/routes.py
index 20cf99c..b1d1fd6 100644
--- a/Merge/back_trm/app/routes.py
+++ b/Merge/back_trm/app/routes.py
@@ -17,9 +17,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
request.remote_addr)
@@ -27,12 +27,12 @@
res=f.giveadmin(data['targetid'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f"尝试修改用户{data['targetid']}角色为admin失败,用户不存在",
request.remote_addr)
return jsonify({'status': 'error', 'message': 'User not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'behavior',
f'用户角色为admin修改成功,用户ID: {data["targetid"]} 被修改为管理员',
request.remote_addr)
@@ -46,9 +46,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
request.remote_addr)
@@ -56,12 +56,12 @@
res=f.giveuser(data['targetid'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f"尝试修改用户{data['targetid']}为user失败,用户不存在",
request.remote_addr)
return jsonify({'status': 'error', 'message': 'User not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'behavior',
f'用户角色修改成功,用户ID: {data["targetid"]} 被修改为普通用户',
request.remote_addr)
@@ -76,9 +76,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要超级管理员才能执行修改用户角色的操作,但是当前用户不是超级管理员',
request.remote_addr)
@@ -86,12 +86,12 @@
res=f.givesuperadmin(data['targetid'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f'尝试修改用户{data["targetid"]}角色为superadmin失败,用户不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'User not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'behavior',
f'用户角色修改成功,用户ID: {data["targetid"]} 被修改为超级管理员',
request.remote_addr)
@@ -105,9 +105,11 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
+ print("+++++++++++++++++++++++++++++++++++++++++++++++++")
+ print(checres)
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要超级管理员才能执行获取用户列表的操作,但是当前用户不是超级管理员',
request.remote_addr)
@@ -121,7 +123,7 @@
'role': datai[2]
})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'access',
'获取用户列表成功',
request.remote_addr)
@@ -135,9 +137,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'admin')
+ checres,userid=f.checkid(data['userid'],'admin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行获取帖子列表的操作,但是当前用户不是管理员',
request.remote_addr)
@@ -150,7 +152,7 @@
'title': datai[1],
'status': datai[2]
})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'access',
'获取帖子列表成功',
request.remote_addr)
@@ -163,21 +165,21 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'admin')
+ checres,userid=f.checkid(data['userid'],'admin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行获取帖子详情的操作,但是当前用户不是管理员',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'Unauthorized'})
res=f.getpost(data['postid'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f'尝试获取帖子{data["postid"]}失败,帖子不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'Post not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'access',
f'获取帖子详情成功,帖子ID: {data["postid"]}',
request.remote_addr)
@@ -190,9 +192,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'admin')
+ checres,userid=f.checkid(data['userid'],'admin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行帖子审核的操作,但是当前用户不是管理员',
request.remote_addr)
@@ -200,12 +202,12 @@
res=f.review(data['postid'],data['status'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f'尝试审核帖子{data["postid"]}失败,帖子不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'Post not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'behavior',
f'帖子审核成功,帖子ID: {data["postid"]} 状态更新为 {data["status"]}',
request.remote_addr)
@@ -220,9 +222,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'admin')
+ checres,userid=f.checkid(data['userid'],'admin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行Nginx认证的操作,但是当前用户不是管理员',
request.remote_addr)
@@ -230,12 +232,12 @@
res=f.nginxauth(data['postid'],data['status'])
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
f'尝试更新Nginx认证状态失败,帖子{data["postid"]}不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'Post not found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'behavior',
f'Nginx认证状态更新成功,帖子ID: {data["postid"]} 状态更新为 {data["status"]}',
request.remote_addr)
@@ -248,9 +250,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行获取系统性能消耗的操作,但是当前用户不是管理员',
request.remote_addr)
@@ -258,13 +260,13 @@
res=f.getsyscost()
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'尝试获取系统性能消耗数据失败,数据不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'No performance data found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'access',
'获取系统性能消耗数据成功',
request.remote_addr)
@@ -287,9 +289,9 @@
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()
f=Fpost(session)
- checres=f.checkid(data['userid'],'superadmin')
+ checres,userid=f.checkid(data['userid'],'superadmin')
if(not checres):
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'系统需要管理员才能执行获取日志的操作,但是当前用户不是管理员',
request.remote_addr)
@@ -297,13 +299,13 @@
res=f.getrecordlog()
if not res:
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'error',
'尝试获取日志失败,日志不存在',
request.remote_addr)
return jsonify({'status': 'error', 'message': 'No logs found'})
- f.recordlog(data['userid'],
+ f.recordlog(userid,
'access',
'获取日志成功',
request.remote_addr)
diff --git a/Merge/back_trm/config.py b/Merge/back_trm/config.py
index d4a2e88..424b53e 100644
--- a/Merge/back_trm/config.py
+++ b/Merge/back_trm/config.py
@@ -9,4 +9,5 @@
SQLPORT=os.getenv('SQLPORT')
SQLNAME=os.getenv('SQLNAME')
SQLUSER=os.getenv('SQLUSER')
- SQLPWD=os.getenv('SQLPWD')
\ No newline at end of file
+ SQLPWD=os.getenv('SQLPWD')
+ API_BASE_URL = 'http://10.126.59.25:8082'
\ No newline at end of file
diff --git a/Merge/back_wzy/__pycache__/config.cpython-310.pyc b/Merge/back_wzy/__pycache__/config.cpython-310.pyc
index 325d84e..bd938d3 100644
--- a/Merge/back_wzy/__pycache__/config.cpython-310.pyc
+++ b/Merge/back_wzy/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/Merge/back_wzy/config.py b/Merge/back_wzy/config.py
index 205b03d..6a9bf8c 100644
--- a/Merge/back_wzy/config.py
+++ b/Merge/back_wzy/config.py
@@ -1,12 +1,12 @@
# config.py
import os
-
+from dotenv import load_dotenv
+load_dotenv()
basedir = os.path.abspath(os.path.dirname(__file__))
-
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY', 'you-will-never-guess')
SQLALCHEMY_DATABASE_URI = os.environ.get(
'SQLURL'
)
SQLALCHEMY_TRACK_MODIFICATIONS = False
- SQLURL=os.getenv('SQLURL')
+ SQLURL = os.getenv('SQLURL')
\ No newline at end of file
diff --git a/Merge/front/src/api/posts_trm.js b/Merge/front/src/api/posts_trm.js
index f28ec19..43d7105 100644
--- a/Merge/front/src/api/posts_trm.js
+++ b/Merge/front/src/api/posts_trm.js
@@ -1,16 +1,20 @@
+import { getAuthToken } from '../utils/auth'
const BASE = 'http://10.126.59.25:5713' // 后端地址
+
+
/**
* 获取待审核的帖子列表
* POST /apostlist
* @param {number|string} userId 平台管理员的用户 ID
* @returns Promise<[ {id, title, status}, … ]>
*/
-export async function fetchPosts(userId) {
+export async function fetchPosts() {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/apostlist`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId })
+ body: JSON.stringify({ userid })
})
if (!res.ok) throw new Error(`fetchPosts: ${res.status}`)
@@ -43,11 +47,12 @@
* 审核通过
* POST /areview
*/
-export async function approvePost(postId, userId) {
+export async function approvePost(postId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/areview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, postid: postId, status: 'published' })
+ body: JSON.stringify({ userid, postid: postId, status: 'published' })
})
if (!res.ok) throw new Error(`approvePost: ${res.status}`)
return res.json()
@@ -57,11 +62,12 @@
* 驳回
* POST /areview
*/
-export async function rejectPost(postId, userId) {
+export async function rejectPost(postId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/areview`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, postid: postId, status: 'rejected' })
+ body: JSON.stringify({ userid, postid: postId, status: 'rejected' })
})
if (!res.ok) throw new Error(`rejectPost: ${res.status}`)
return res.json()
@@ -74,11 +80,12 @@
* @param {number|string} userId 平台管理员的用户 ID
* @returns Promise<{id, title, content, status}>
*/
-export async function fetchPost(postId, userId) {
+export async function fetchPost(postId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/agetpost`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, postid: postId })
+ body: JSON.stringify({ userid, postid: postId })
})
if (!res.ok) throw new Error(`fetchPost: ${res.status}`)
return res.json()
@@ -90,41 +97,45 @@
* @param {number|string} userId 平台管理员的用户 ID
* @returns Promise<[ {id, name, role}, … ]>
*/
-export async function fetchUserList(userId) {
+export async function fetchUserList() {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/sgetuserlist`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId })
+ body: JSON.stringify({ userid })
})
if (!res.ok) throw new Error(`fetchUserList: ${res.status}`)
return res.json()
}
-export async function giveAdmin(userId, targetId) {
+export async function giveAdmin(targetId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/sgiveadmin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, targetid: targetId })
+ body: JSON.stringify({ userid, targetid: targetId })
})
if (!res.ok) throw new Error(`giveAdmin: ${res.status}`)
return res.json()
}
-export async function giveSuperAdmin(userId, targetId) {
+export async function giveSuperAdmin(targetId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/sgivesuperadmin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, targetid: targetId })
+ body: JSON.stringify({ userid, targetid: targetId })
})
if (!res.ok) throw new Error(`giveSuperAdmin: ${res.status}`)
return res.json()
}
-export async function giveUser(userId, targetId) {
+export async function giveUser(targetId) {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/sgiveuser`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId, targetid: targetId })
+ body: JSON.stringify({ userid, targetid: targetId })
})
if (!res.ok) throw new Error(`giveUser: ${res.status}`)
return res.json()
@@ -133,14 +144,14 @@
/**
* 获取事务日志
* POST /getrecordlog
- * @param {number|string} userId 平台管理员的用户 ID
* @returns Promise<[ {id, user_id, type, content, ip, created_at}, … ]>
*/
-export async function fetchRecordLog(userId) {
+export async function fetchRecordLog() {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/getrecordlog`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId })
+ body: JSON.stringify({ userid })
})
if (!res.ok) throw new Error(`fetchRecordLog: ${res.status}`)
const json = await res.json()
@@ -166,11 +177,12 @@
* @param {number|string} userId 平台管理员的用户 ID
* @returns Promise<[ {id, record_time, endpoint, elapsed_time, cpu_user, cpu_system, memory_rss}, … ]>
*/
-export async function fetchSysCost(userId) {
+export async function fetchSysCost() {
+ const userid = getAuthToken()
const res = await fetch(`${BASE}/getsyscost`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userid: userId })
+ body: JSON.stringify({ userid })
})
if (!res.ok) throw new Error(`fetchSysCost: ${res.status}`)
const json = await res.json()
diff --git a/Merge/front/src/components/PerformanceLogs.js b/Merge/front/src/components/PerformanceLogs.js
index be9eb99..00f7e7d 100644
--- a/Merge/front/src/components/PerformanceLogs.js
+++ b/Merge/front/src/components/PerformanceLogs.js
@@ -7,8 +7,10 @@
function PerformanceLogs({ userId }) {
const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(true);
useEffect(() => {
+ setLoading(true);
fetchSysCost(userId)
.then(list => {
const msList = list.map(item => ({
@@ -22,9 +24,40 @@
console.log('Converted data:', msList[0]); // debug first item
setData(msList);
})
- .catch(err => console.error('fetchSysCost error:', err));
+ .catch(err => console.error('fetchSysCost error:', err))
+ .finally(() => setLoading(false));
}, [userId]);
+ if (loading) {
+ return (
+ <section className="dashboard-performance">
+ <div style={{
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '400px',
+ flexDirection: 'column'
+ }}>
+ <div style={{
+ border: '4px solid #f3f3f3',
+ borderTop: '4px solid #3498db',
+ borderRadius: '50%',
+ width: '50px',
+ height: '50px',
+ animation: 'spin 1s linear infinite'
+ }}></div>
+ <p style={{ marginTop: '20px', color: '#666' }}>加载中...</p>
+ <style>{`
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+ `}</style>
+ </div>
+ </section>
+ );
+ }
+
return (
<section className="dashboard-performance">
{/* 响应时间图表 */}
diff --git a/Merge/front/src/components/TransactionLogs.js b/Merge/front/src/components/TransactionLogs.js
index 9df8f67..24a31cb 100644
--- a/Merge/front/src/components/TransactionLogs.js
+++ b/Merge/front/src/components/TransactionLogs.js
@@ -2,14 +2,76 @@
import { fetchRecordLog } from '../api/posts_trm';
function TransactionLogs({ userId }) {
- const [records, setRecords] = useState([]);
+ const [allRecords, setAllRecords] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pageSize] = useState(10);
+ const [loading, setLoading] = useState(false);
+
+ const loadRecords = async () => {
+ setLoading(true);
+ try {
+ const data = await fetchRecordLog();
+ setAllRecords(data);
+ } catch (err) {
+ console.error('fetchRecordLog error:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
useEffect(() => {
- fetchRecordLog(userId)
- .then(data => setRecords(data))
- .catch(err => console.error('fetchRecordLog error:', err));
+ loadRecords();
}, [userId]);
+ const total = allRecords.length;
+ const totalPages = Math.ceil(total / pageSize);
+
+ // 计算当前页显示的数据
+ const startIndex = (currentPage - 1) * pageSize;
+ const endIndex = startIndex + pageSize;
+ const currentRecords = allRecords.slice(startIndex, endIndex);
+
+ const handlePrevPage = () => {
+ if (currentPage > 1) {
+ setCurrentPage(currentPage - 1);
+ }
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < totalPages) {
+ setCurrentPage(currentPage + 1);
+ }
+ };
+
+ const handlePageClick = (page) => {
+ setCurrentPage(page);
+ };
+
+ const renderPageNumbers = () => {
+ const pages = [];
+ const maxVisiblePages = 5;
+
+ let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
+ let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
+
+ if (endPage - startPage < maxVisiblePages - 1) {
+ startPage = Math.max(1, endPage - maxVisiblePages + 1);
+ }
+
+ for (let i = startPage; i <= endPage; i++) {
+ pages.push(
+ <button
+ key={i}
+ onClick={() => handlePageClick(i)}
+ className={`page-btn ${currentPage === i ? 'active' : ''}`}
+ >
+ {i}
+ </button>
+ );
+ }
+ return pages;
+ };
+
return (
<section className="dashboard-logs">
<table className="admin-table">
@@ -24,25 +86,96 @@
</tr>
</thead>
<tbody>
- {records.length > 0
- ? records.map((r, i) => (
- <tr key={i}>
- <td>{r.id}</td>
- <td>{r.user_id}</td>
- <td>{r.type}</td>
- <td>{r.content}</td>
- <td>{r.ip}</td>
- <td>{new Date(r.created_at).toLocaleString()}</td>
- </tr>
- ))
- : (
- <tr>
- <td colSpan="6" style={{ textAlign: 'center' }}>暂无数据</td>
+ {loading ? (
+ <tr>
+ <td colSpan="6" style={{ textAlign: 'center' }}>加载中...</td>
+ </tr>
+ ) : currentRecords.length > 0 ? (
+ currentRecords.map((r, i) => (
+ <tr key={r.id || i}>
+ <td>{r.id}</td>
+ <td>{r.user_id}</td>
+ <td>{r.type}</td>
+ <td>{r.content}</td>
+ <td>{r.ip}</td>
+ <td>{new Date(r.created_at).toLocaleString()}</td>
</tr>
- )
- }
+ ))
+ ) : (
+ <tr>
+ <td colSpan="6" style={{ textAlign: 'center' }}>暂无数据</td>
+ </tr>
+ )}
</tbody>
</table>
+
+ {totalPages > 1 && (
+ <div className="pagination">
+ <button
+ onClick={handlePrevPage}
+ disabled={currentPage === 1}
+ className="page-btn"
+ >
+ 上一页
+ </button>
+
+ {renderPageNumbers()}
+
+ <button
+ onClick={handleNextPage}
+ disabled={currentPage === totalPages}
+ className="page-btn"
+ >
+ 下一页
+ </button>
+
+ <span className="page-info">
+ 第 {currentPage} 页,共 {totalPages} 页,总计 {total} 条记录
+ </span>
+ </div>
+ )}
+
+ <style jsx>{`
+ .pagination {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 16px;
+ flex-wrap: wrap;
+ }
+
+ .page-btn {
+ padding: 6px 12px;
+ border: 1px solid #ddd;
+ background: white;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s;
+ }
+
+ .page-btn:hover:not(:disabled) {
+ background: #f5f5f5;
+ border-color: #999;
+ }
+
+ .page-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ .page-btn.active {
+ background: #007bff;
+ color: white;
+ border-color: #007bff;
+ }
+
+ .page-info {
+ margin-left: 16px;
+ font-size: 14px;
+ color: #666;
+ }
+ `}</style>
</section>
);
}
diff --git a/Merge/front/src/components/UserProfile.jsx b/Merge/front/src/components/UserProfile.jsx
index 8421997..53618db 100644
--- a/Merge/front/src/components/UserProfile.jsx
+++ b/Merge/front/src/components/UserProfile.jsx
@@ -29,7 +29,8 @@
useTheme,
CircularProgress,
Snackbar,
- Alert
+ Alert,
+ Menu
} from '@mui/material';
import { useParams } from 'react-router-dom';
import {
@@ -123,6 +124,7 @@
const [isEditing, setIsEditing] = useState(false);
const [followers, setFollowers] = useState([]);
const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
+ const [anchorEl, setAnchorEl] = useState(null);
// 用户数据状态
const [currentUser, setCurrentUser] = useState(null);
@@ -149,6 +151,8 @@
location: ''
});
+ const menuOpen = Boolean(anchorEl);
+
// 显示提示信息
const showSnackbar = (message, severity = 'success') => {
setSnackbar({ open: true, message, severity });
@@ -390,6 +394,16 @@
navigate(`/user/${userId}`);
};
+ const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
+ const handleMenuClose = () => setAnchorEl(null);
+ const handleLogout = () => {
+ handleMenuClose();
+ // 清理本地存储
+ localStorage.clear();
+ // 在此处添加退出登录逻辑,比如清理本地存储并跳转
+ navigate('/login');
+ };
+
if (loading) {
return (
<Box sx={{
@@ -531,9 +545,18 @@
>
{profileUser.is_following ? '已关注' : '关注'}
</Button>
- <IconButton sx={{ ml: 1 }}>
+ <IconButton sx={{ ml: 1 }} onClick={handleMenuOpen}>
<MoreVert />
</IconButton>
+ <Menu
+ anchorEl={anchorEl}
+ open={menuOpen}
+ onClose={handleMenuClose}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
+ >
+ <MenuItem onClick={handleLogout}>退出登录</MenuItem>
+ </Menu>
</>
)}
</Box>