| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  | import os | 
					
						
							|  |  |  |  | import datetime | 
					
						
							|  |  |  |  | from pathlib import Path | 
					
						
							|  |  |  |  | from typing import Dict | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | def save_face_to_up_images( | 
					
						
							|  |  |  |  |         client_ip: str, | 
					
						
							|  |  |  |  |         face_name: str, | 
					
						
							|  |  |  |  |         image_bytes: bytes, | 
					
						
							|  |  |  |  |         image_format: str = "jpg" | 
					
						
							|  |  |  |  | ) -> Dict[str, str]: | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     保存人脸图片到 `/up_images/用户IP/人脸名字/` 路径 | 
					
						
							| 
									
										
										
										
											2025-09-12 20:09:07 +08:00
										 |  |  |  |     确保db_path以up_images开头,且统一使用正斜杠 | 
					
						
							| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     参数: | 
					
						
							|  |  |  |  |         client_ip: 客户端IP(原始格式,如192.168.1.101) | 
					
						
							|  |  |  |  |         face_name: 人脸名称(用户输入,可为空) | 
					
						
							|  |  |  |  |         image_bytes: 人脸图片二进制数据 | 
					
						
							|  |  |  |  |         image_format: 图片格式(默认jpg) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     返回: | 
					
						
							|  |  |  |  |         字典:success(是否成功)、db_path(存数据库的相对路径)、local_abs_path(本地绝对路径)、msg(提示) | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     try: | 
					
						
							|  |  |  |  |         # 1. 基础参数校验 | 
					
						
							|  |  |  |  |         if not client_ip.strip(): | 
					
						
							|  |  |  |  |             return {"success": False, "db_path": "", "local_abs_path": "", "msg": "客户端IP不能为空"} | 
					
						
							|  |  |  |  |         if not image_bytes: | 
					
						
							|  |  |  |  |             return {"success": False, "db_path": "", "local_abs_path": "", "msg": "图片二进制数据为空"} | 
					
						
							|  |  |  |  |         if image_format.lower() not in ["jpg", "jpeg", "png"]: | 
					
						
							|  |  |  |  |             return {"success": False, "db_path": "", "local_abs_path": "", "msg": "仅支持jpg/jpeg/png格式"} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 2. 处理特殊字符(避免路径错误) | 
					
						
							|  |  |  |  |         safe_ip = client_ip.strip().replace(".", "_")  # IP中的.替换为_ | 
					
						
							|  |  |  |  |         safe_face_name = face_name.strip() if (face_name and face_name.strip()) else "未命名" | 
					
						
							|  |  |  |  |         safe_face_name = "".join([c for c in safe_face_name if c not in r'\/:*?"<>|'])  # 过滤非法字符 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 3. 构建根目录(强制转为绝对路径,避免相对路径混淆) | 
					
						
							| 
									
										
										
										
											2025-09-12 20:09:07 +08:00
										 |  |  |  |         root_dir = Path("up_images").resolve()  # 转为绝对路径(如D:/Git/bin/video/up_images) | 
					
						
							| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  |         if not root_dir.exists(): | 
					
						
							|  |  |  |  |             root_dir.mkdir(parents=True, exist_ok=True) | 
					
						
							|  |  |  |  |             print(f"[FileUtil] 已创建up_images根目录:{root_dir}") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 4. 构建文件层级路径(确保在root_dir子目录下) | 
					
						
							|  |  |  |  |         ip_dir = root_dir / safe_ip | 
					
						
							|  |  |  |  |         face_name_dir = ip_dir / safe_face_name | 
					
						
							|  |  |  |  |         face_name_dir.mkdir(parents=True, exist_ok=True)  # 自动创建目录 | 
					
						
							|  |  |  |  |         print(f"[FileUtil] 图片存储目录:{face_name_dir}") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 5. 生成唯一文件名(毫秒级时间戳) | 
					
						
							|  |  |  |  |         timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3] | 
					
						
							|  |  |  |  |         image_filename = f"face_{safe_ip}_{timestamp}.{image_format.lower()}" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         # 6. 计算路径(确保所有路径都是绝对路径且在root_dir下) | 
					
						
							| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  |         local_abs_path = face_name_dir / image_filename  # 绝对路径 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 验证路径是否在root_dir下(防止路径穿越攻击) | 
					
						
							|  |  |  |  |         if not local_abs_path.resolve().is_relative_to(root_dir.resolve()): | 
					
						
							|  |  |  |  |             raise Exception(f"图片路径不在up_images根目录下(安全校验失败):{local_abs_path}") | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 20:09:07 +08:00
										 |  |  |  |         # 数据库存储路径:强制包含up_images前缀,统一使用正斜杠 | 
					
						
							|  |  |  |  |         relative_path = local_abs_path.relative_to(root_dir.parent)  # 相对于root_dir的父目录 | 
					
						
							|  |  |  |  |         db_path = str(relative_path).replace("\\", "/")  # 此时会包含up_images部分 | 
					
						
							| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         # 7. 写入图片文件 | 
					
						
							|  |  |  |  |         with open(local_abs_path, "wb") as f: | 
					
						
							|  |  |  |  |             f.write(image_bytes) | 
					
						
							|  |  |  |  |         print(f"[FileUtil] 图片保存成功:") | 
					
						
							|  |  |  |  |         print(f"  数据库路径:{db_path}") | 
					
						
							|  |  |  |  |         print(f"  本地绝对路径:{local_abs_path}") | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return { | 
					
						
							|  |  |  |  |             "success": True, | 
					
						
							| 
									
										
										
										
											2025-09-12 20:09:07 +08:00
										 |  |  |  |             "db_path": db_path,  # 格式为 up_images/192_168_110_31/小龙/xxx.jpg | 
					
						
							|  |  |  |  |             "local_abs_path": str(local_abs_path),  # 本地绝对路径(完整路径) | 
					
						
							| 
									
										
										
										
											2025-09-12 14:05:09 +08:00
										 |  |  |  |             "msg": "图片保存成功" | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     except Exception as e: | 
					
						
							|  |  |  |  |         error_msg = f"图片保存失败:{str(e)}" | 
					
						
							|  |  |  |  |         print(f"[FileUtil] 错误:{error_msg}") | 
					
						
							| 
									
										
										
										
											2025-09-12 18:28:43 +08:00
										 |  |  |  |         return {"success": False, "db_path": "", "local_abs_path": "", "msg": error_msg} |