TRM-coding | 882dc44 | 2025-06-18 20:13:21 +0800 | [diff] [blame] | 1 | from models.user import User as users |
| 2 | from models.post import Post as post |
| 3 | import secrets |
| 4 | import hashlib |
| 5 | from datetime import datetime, timedelta |
| 6 | from sqlalchemy.orm import Session |
| 7 | from models.logs import Log |
| 8 | from models.syscost import PerformanceData |
TRM-coding | f55d237 | 2025-06-20 16:22:37 +0800 | [diff] [blame] | 9 | import os |
| 10 | import requests |
| 11 | from werkzeug.utils import secure_filename |
| 12 | import uuid |
| 13 | |
TRM-coding | 882dc44 | 2025-06-18 20:13:21 +0800 | [diff] [blame] | 14 | class Fpost: |
| 15 | def __init__(self,session:Session): |
| 16 | self.session=session |
TRM-coding | f55d237 | 2025-06-20 16:22:37 +0800 | [diff] [blame] | 17 | # 配置文件存储节点(docker容器的实际路径) |
| 18 | self.storage_nodes = [ |
| 19 | '/home/tianruiming/docker02/static', |
| 20 | '/home/tianruiming/docker03/static', |
| 21 | '/home/tianruiming/docker04/static' |
| 22 | ] |
| 23 | # 默认访问节点(LVS负载均衡器) |
trm | 11b1197 | 2025-06-20 09:48:51 +0000 | [diff] [blame] | 24 | self.access_node = '192.168.5.200:8080' |
TRM-coding | 882dc44 | 2025-06-18 20:13:21 +0800 | [diff] [blame] | 25 | 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-coding | f55d237 | 2025-06-20 16:22:37 +0800 | [diff] [blame] | 169 | 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 | |
| 180 | for file in files: |
| 181 | if file and file.filename: |
| 182 | # 生成安全的文件名 |
| 183 | original_filename = secure_filename(file.filename) |
| 184 | # 生成唯一文件名避免冲突 |
| 185 | unique_id = str(uuid.uuid4()) |
956303669 | f3699a1 | 2025-06-21 16:40:05 +0800 | [diff] [blame] | 186 | file_extension = os.path.splitext(original_filename)[1] |
TRM-coding | f55d237 | 2025-06-20 16:22:37 +0800 | [diff] [blame] | 187 | unique_filename = f"{unique_id}{file_extension}" |
| 188 | |
956303669 | f3699a1 | 2025-06-21 16:40:05 +0800 | [diff] [blame] | 189 | # 读取文件内容 |
TRM-coding | f55d237 | 2025-06-20 16:22:37 +0800 | [diff] [blame] | 190 | file_content = file.read() |
| 191 | file.seek(0) # 重置文件指针 |
| 192 | |
| 193 | # 保存到所有存储节点 |
| 194 | success_count = 0 |
| 195 | for node_path in self.storage_nodes: |
| 196 | try: |
| 197 | # 创建目录路径:/home/tianruiming/docker0X/static/{post_id}/ |
| 198 | node_dir = os.path.join(node_path, str(post_id)) |
| 199 | os.makedirs(node_dir, exist_ok=True) |
| 200 | |
| 201 | # 完整文件路径 |
| 202 | full_file_path = os.path.join(node_dir, unique_filename) |
| 203 | |
| 204 | # 写入文件到存储节点 |
| 205 | with open(full_file_path, 'wb') as f: |
| 206 | f.write(file_content) |
| 207 | |
| 208 | success_count += 1 |
| 209 | print(f"Successfully saved file to {full_file_path}") |
| 210 | |
| 211 | except Exception as e: |
| 212 | print(f"Failed to save file to node {node_path}: {str(e)}") |
| 213 | |
| 214 | if success_count > 0: |
| 215 | # 生成访问URL,格式:http://192.168.5.231:8080/static/{post_id}/{filename} |
| 216 | media_url = f"http://{self.access_node}/static/{post_id}/{unique_filename}" |
| 217 | media_urls.append(media_url) |
| 218 | else: |
| 219 | raise Exception(f"Failed to save file {original_filename} to any storage node") |
| 220 | |
| 221 | return media_urls |
| 222 | |
| 223 | def create_post_with_files(self, user_id, title, content, topic_id, status, files): |
| 224 | """ |
| 225 | 创建带文件的帖子 |
| 226 | :param user_id: 用户ID |
| 227 | :param title: 标题 |
| 228 | :param content: 内容 |
| 229 | :param topic_id: 话题ID |
| 230 | :param status: 状态 |
| 231 | :param files: 文件列表 |
| 232 | :return: 帖子对象 |
| 233 | """ |
| 234 | # 先创建帖子获取ID |
| 235 | new_post = post( |
| 236 | user_id=user_id, |
| 237 | title=title, |
| 238 | content=content, |
| 239 | topic_id=topic_id if topic_id else None, |
| 240 | status=status, |
| 241 | created_at=datetime.now(), |
| 242 | updated_at=datetime.now() |
| 243 | ) |
| 244 | |
| 245 | self.session.add(new_post) |
| 246 | self.session.flush() # 获取ID但不提交 |
| 247 | |
| 248 | # 保存文件 |
| 249 | if files: |
| 250 | media_urls = self.save_files_to_storage(files, new_post.id) |
| 251 | new_post.media_urls = media_urls |
| 252 | else: |
| 253 | new_post.media_urls = [] |
| 254 | |
| 255 | self.session.commit() |
| 256 | return new_post |
| 257 | |
| 258 | def update_post_with_files(self, post_id, title=None, content=None, topic_id=None, status=None, files=None, existing_media_urls=None): |
| 259 | """ |
| 260 | 更新带文件的帖子 |
| 261 | :param post_id: 帖子ID |
| 262 | :param title: 标题 |
| 263 | :param content: 内容 |
| 264 | :param topic_id: 话题ID |
| 265 | :param status: 状态 |
| 266 | :param files: 新文件列表 |
| 267 | :param existing_media_urls: 现有媒体URL |
| 268 | :return: 更新后的帖子对象 |
| 269 | """ |
| 270 | post_obj = self.session.query(post).filter(post.id == post_id).first() |
| 271 | if not post_obj: |
| 272 | return None |
| 273 | |
| 274 | # 更新基本信息 |
| 275 | if title is not None: |
| 276 | post_obj.title = title |
| 277 | if content is not None: |
| 278 | post_obj.content = content |
| 279 | if topic_id is not None: |
| 280 | post_obj.topic_id = topic_id |
| 281 | if status is not None: |
| 282 | post_obj.status = status |
| 283 | |
| 284 | post_obj.updated_at = datetime.now() |
| 285 | |
| 286 | # 处理文件 |
| 287 | if files: |
| 288 | # 有新文件,保存新文件 |
| 289 | media_urls = self.save_files_to_storage(files, post_id) |
| 290 | post_obj.media_urls = media_urls |
| 291 | elif existing_media_urls is not None: |
| 292 | # 保留现有媒体URL |
| 293 | post_obj.media_urls = existing_media_urls |
| 294 | |
| 295 | self.session.commit() |
| 296 | return post_obj |