blob: 2ed295f7f4017d4542236cfe18e179fee33da957 [file] [log] [blame]
TRM-coding882dc442025-06-18 20:13:21 +08001from models.user import User as users
2from models.post import Post as post
3import secrets
4import hashlib
5from datetime import datetime, timedelta
6from sqlalchemy.orm import Session
7from models.logs import Log
8from models.syscost import PerformanceData
TRM-codingf55d2372025-06-20 16:22:37 +08009import os
10import requests
11from werkzeug.utils import secure_filename
12import uuid
13
TRM-coding882dc442025-06-18 20:13:21 +080014class Fpost:
15 def __init__(self,session:Session):
16 self.session=session
TRM-codingf55d2372025-06-20 16:22:37 +080017 # 配置文件存储节点(docker容器的实际路径)
18 self.storage_nodes = [
19 '/home/tianruiming/docker02/static',
20 '/home/tianruiming/docker03/static',
21 '/home/tianruiming/docker04/static'
22 ]
23 # 默认访问节点(LVS负载均衡器)
trm11b11972025-06-20 09:48:51 +000024 self.access_node = '192.168.5.200:8080'
TRM-coding882dc442025-06-18 20:13:21 +080025 return
26
27
28 def getlist(self):
29 results = self.session.query(post.id, post.title,post.status)
30 return results
31
32 def getuserlist(self):
33 results= self.session.query(users.id, users.username, users.role)
34 return results
35
36 def giveadmin(self,userid):
37 res=self.session.query(users).filter(users.id==userid).first()
38 if not res:
39 return False
40 res.role='admin'
41 self.session.commit()
42 return True
43
44 def giveuser(self,userid):
45 res=self.session.query(users).filter(users.id==userid).first()
46 if not res:
47 return False
48 res.role='user'
49 self.session.commit()
50 return True
51
52 def givesuperadmin(self,userid):
53 res=self.session.query(users).filter(users.id==userid).first()
54 if not res:
55 return False
56 res.role='superadmin'
57 self.session.commit()
58 return True
59
60
61 def getpost(self,postid):
62 res=self.session.query(post).filter(post.id==postid).first()
63 return res
64 def checkid(self,userid,status=''):
65 res=self.session.query(users).filter(users.id==userid).first()
66 if(not res):
67 return False
68 if res.role !=status:
69 return False
70 return True
71
72 def review(self,postid,status):
73 print(status)
74 res=self.session.query(post).filter(post.id==postid).first()
75 if not res:
76 return False
77 res.status=status
78 self.session.commit()
79 return True
80
81 def createtoken(self, userid):
82 """
83 根据userid创建token并插入到数据库
84 :param userid: 用户ID
85 :return: 生成的token字符串
86 """
87 # 生成随机盐值
88 salt = secrets.token_hex(16)
89
90 # 创建哈希值:userid + 当前时间戳 + 随机盐值
91 current_time = str(datetime.now().timestamp())
92 hash_input = f"{userid}_{current_time}_{salt}"
93
94 # 生成SHA256哈希值作为token
95 token = hashlib.sha256(hash_input.encode()).hexdigest()
96
97 # 设置时间
98 created_time = datetime.now()
99 expires_time = created_time + timedelta(days=1) # 一天后过期
100
101 try:
102 # 创建新的token记录
103 new_token = Token(
104 token=token,
105 expires_at=expires_time,
106 created_at=created_time
107 )
108
109 # 假设self.session是数据库会话对象
110 self.session.add(new_token)
111 self.session.commit()
112
113 return token
114
115 except Exception as e:
116 self.session.rollback()
117 raise Exception(f"创建token失败: {str(e)}")
118
119 def recordlog(self,user_id,log_type,content,ip):
120 """
121 记录日志
122 :param user_id: 用户ID
123 :param log_type: 日志类型,'access','error','behavior','system'
124 :param content: 日志内容
125 :param ip: IP地址
126 """
127 try:
128 new_log = Log(
129 user_id=user_id,
130 type=log_type,
131 content=content,
132 ip=ip
133 )
134 self.session.add(new_log)
135 self.session.commit()
136 except Exception as e:
137 self.session.rollback()
138 raise Exception(f"记录日志失败: {str(e)}")
139
140 def getrecordlog(self):
141 res= self.session.query(Log).all()
142 return res
143
144 def recordsyscost(self, endpoint: str, elapsed_time: float, cpu_user: float, cpu_system: float, memory_rss: int):
145 """
146 记录系统性能消耗到 performance_data 表
147 :param endpoint: 请求接口路径
148 :param elapsed_time: 总耗时(秒)
149 :param cpu_user: 用户态 CPU 时间差(秒)
150 :param cpu_system: 系统态 CPU 时间差(秒)
151 :param memory_rss: RSS 内存增量(字节)
152 """
153 try:
154 new_record = PerformanceData(
155 endpoint=endpoint,
156 elapsed_time=elapsed_time,
157 cpu_user=cpu_user,
158 cpu_system=cpu_system,
159 memory_rss=memory_rss
160 )
161 self.session.add(new_record)
162 self.session.commit()
163 except Exception as e:
164 self.session.rollback()
165 raise Exception(f"记录系统性能消耗失败: {e}")
166
167 def getsyscost(self):
168 res= self.session.query(PerformanceData).all()
TRM-codingf55d2372025-06-20 16:22:37 +0800169 return res
170
171 def save_files_to_storage(self, files, post_id):
172 """
173 将文件保存到所有存储节点
174 :param files: 文件列表
175 :param post_id: 帖子ID
176 :return: 媒体URL列表
177 """
178 media_urls = []
179
9563036699de8c092025-06-21 16:41:18 +0800180 # 支持的文件类型
181 ALLOWED_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.webp'}
182 ALLOWED_VIDEO_EXTENSIONS = {'.mp4', '.mov', '.avi'}
183 ALLOWED_EXTENSIONS = ALLOWED_IMAGE_EXTENSIONS | ALLOWED_VIDEO_EXTENSIONS
184
TRM-codingf55d2372025-06-20 16:22:37 +0800185 for file in files:
186 if file and file.filename:
187 # 生成安全的文件名
188 original_filename = secure_filename(file.filename)
189 # 生成唯一文件名避免冲突
190 unique_id = str(uuid.uuid4())
9563036699de8c092025-06-21 16:41:18 +0800191 file_extension = os.path.splitext(original_filename)[1].lower()
192
193 # 验证文件类型
194 if file_extension not in ALLOWED_EXTENSIONS:
195 raise Exception(f"不支持的文件类型: {file_extension}")
196
TRM-codingf55d2372025-06-20 16:22:37 +0800197 unique_filename = f"{unique_id}{file_extension}"
198
9563036699de8c092025-06-21 16:41:18 +0800199 # 读取文件内容(对于大文件,分块读取)
TRM-codingf55d2372025-06-20 16:22:37 +0800200 file_content = file.read()
201 file.seek(0) # 重置文件指针
202
9563036699de8c092025-06-21 16:41:18 +0800203 # 验证文件大小
204 file_size = len(file_content)
205 max_image_size = 32 * 1024 * 1024 # 32MB
206 max_video_size = 2 * 1024 * 1024 * 1024 # 2GB
207
208 if file_extension in ALLOWED_IMAGE_EXTENSIONS and file_size > max_image_size:
209 raise Exception(f"图片文件过大: {file_size / (1024*1024):.1f}MB,最大支持32MB")
210 elif file_extension in ALLOWED_VIDEO_EXTENSIONS and file_size > max_video_size:
211 raise Exception(f"视频文件过大: {file_size / (1024*1024*1024):.1f}GB,最大支持2GB")
212
TRM-codingf55d2372025-06-20 16:22:37 +0800213 # 保存到所有存储节点
214 success_count = 0
215 for node_path in self.storage_nodes:
216 try:
217 # 创建目录路径:/home/tianruiming/docker0X/static/{post_id}/
218 node_dir = os.path.join(node_path, str(post_id))
219 os.makedirs(node_dir, exist_ok=True)
220
221 # 完整文件路径
222 full_file_path = os.path.join(node_dir, unique_filename)
223
224 # 写入文件到存储节点
225 with open(full_file_path, 'wb') as f:
226 f.write(file_content)
227
228 success_count += 1
229 print(f"Successfully saved file to {full_file_path}")
230
231 except Exception as e:
232 print(f"Failed to save file to node {node_path}: {str(e)}")
233
234 if success_count > 0:
235 # 生成访问URL,格式:http://192.168.5.231:8080/static/{post_id}/{filename}
236 media_url = f"http://{self.access_node}/static/{post_id}/{unique_filename}"
237 media_urls.append(media_url)
238 else:
239 raise Exception(f"Failed to save file {original_filename} to any storage node")
240
241 return media_urls
242
243 def create_post_with_files(self, user_id, title, content, topic_id, status, files):
244 """
245 创建带文件的帖子
246 :param user_id: 用户ID
247 :param title: 标题
248 :param content: 内容
249 :param topic_id: 话题ID
250 :param status: 状态
251 :param files: 文件列表
252 :return: 帖子对象
253 """
254 # 先创建帖子获取ID
255 new_post = post(
256 user_id=user_id,
257 title=title,
258 content=content,
259 topic_id=topic_id if topic_id else None,
260 status=status,
261 created_at=datetime.now(),
262 updated_at=datetime.now()
263 )
264
265 self.session.add(new_post)
266 self.session.flush() # 获取ID但不提交
267
268 # 保存文件
269 if files:
270 media_urls = self.save_files_to_storage(files, new_post.id)
271 new_post.media_urls = media_urls
272 else:
273 new_post.media_urls = []
274
275 self.session.commit()
276 return new_post
277
278 def update_post_with_files(self, post_id, title=None, content=None, topic_id=None, status=None, files=None, existing_media_urls=None):
279 """
280 更新带文件的帖子
281 :param post_id: 帖子ID
282 :param title: 标题
283 :param content: 内容
284 :param topic_id: 话题ID
285 :param status: 状态
286 :param files: 新文件列表
287 :param existing_media_urls: 现有媒体URL
288 :return: 更新后的帖子对象
289 """
290 post_obj = self.session.query(post).filter(post.id == post_id).first()
291 if not post_obj:
292 return None
293
294 # 更新基本信息
295 if title is not None:
296 post_obj.title = title
297 if content is not None:
298 post_obj.content = content
299 if topic_id is not None:
300 post_obj.topic_id = topic_id
301 if status is not None:
302 post_obj.status = status
303
304 post_obj.updated_at = datetime.now()
305
306 # 处理文件
307 if files:
308 # 有新文件,保存新文件
309 media_urls = self.save_files_to_storage(files, post_id)
310 post_obj.media_urls = media_urls
311 elif existing_media_urls is not None:
312 # 保留现有媒体URL
313 post_obj.media_urls = existing_media_urls
314
315 self.session.commit()
316 return post_obj