1414from apps .base .config import refresh_settings
1515from apps .base .services import get_stored_download , response_from_download , stored_file_of
1616from core .security import INTERNAL_CONFIG_KEYS , generate_jwt_secret
17- from apps .base .models import DeliveryCode , DeliveryFile , FileCodes , KeyValue
17+ from apps .base .models import DeliveryCode , FileCodes , KeyValue
1818from apps .base .utils import get_expire_info
1919from apps .base .local_share import (
2020 LOCAL_REF_MARKER ,
2727 should_skip_storage_delete ,
2828)
2929from apps .base .metadata import normalize_metadata_note , normalize_metadata_tags
30- from apps .base .share_storage import remove_delivery_share , storage_for_share , storage_type_for_share
30+ from apps .base .share_storage import storage_for_share , storage_type_for_share
3131from fastapi import HTTPException
3232from core .utils import get_now , hash_password , is_password_hashed , validate_background_url
3333
@@ -104,9 +104,7 @@ def _file_metadata_key(self, file_id: int) -> str:
104104 return f"{ self .FILE_METADATA_KEY_PREFIX } { file_id } "
105105
106106 async def _delete_file_code (self , file_code : FileCodes ):
107- # 寄件分享在两个管理入口使用相同撤销与清理逻辑,避免重复计费或遗留可用取件码。
108- if await remove_delivery_share (file_code ):
109- return
107+ # 寄件文件与普通文件共用删除流程,失败时保留记录供重试。
110108 # NAS 引用只删除分享记录,不能删除原始文件。
111109 if not should_skip_storage_delete (file_code ):
112110 storage = await storage_for_share (file_code , self ._file_storage )
@@ -227,7 +225,8 @@ async def update_file(
227225 raise HTTPException (status_code = 404 , detail = "文件不存在" )
228226
229227 update_data : dict [str , Any ] = {}
230- if code is not None and code != file_code .code :
228+ # 历史私有文件没有公开口令,普通编辑不能改变其内部标识或公开权限。
229+ if code is not None and not file_code .is_private and code != file_code .code :
231230 if await FileCodes .filter (code = code ).first ():
232231 raise HTTPException (status_code = 400 , detail = "code已存在" )
233232 update_data ["code" ] = code
@@ -502,12 +501,9 @@ async def list_files(
502501 query = FileCodes .all ()
503502 if delivery_id is not None :
504503 # 收件列表复用文件管理的数据与操作,只限定当前管理员选中的寄件码。
505- if not await DeliveryCode .filter (id = delivery_id , owner_id = "admin" ).exists ():
504+ if not await DeliveryCode .filter (id = delivery_id ).exists ():
506505 raise HTTPException (404 , "寄件码不存在" )
507- share_ids = await DeliveryFile .filter (
508- delivery_id = delivery_id , owner_id = "admin" , status = "shared"
509- ).values_list ("share_id" , flat = True )
510- query = query .filter (id__in = share_ids )
506+ query = query .filter (delivery_id = delivery_id )
511507 all_files = await query
512508 now = await get_now ()
513509 enriched_files = []
@@ -610,7 +606,8 @@ async def _build_admin_file_item(
610606 )
611607 data = {
612608 "id" : file_code .id ,
613- "code" : file_code .code ,
609+ "code" : "" if file_code .is_private else file_code .code ,
610+ "is_private" : file_code .is_private ,
614611 "prefix" : file_code .prefix ,
615612 "suffix" : file_code .suffix ,
616613 "uuid_file_name" : file_code .uuid_file_name ,
@@ -685,10 +682,8 @@ async def get_file_detail(self, file_id: int):
685682 is_text = is_text ,
686683 )
687684
688- # 详情展示记录实际后端;历史普通文件没有可靠来源时显示 unknown。
689- actual_storage_type = await storage_type_for_share (file_code )
690- # 未知来源以 NULL 交给前端本地化,不能直接输出英文文案。
691- display_storage_type = actual_storage_type
685+ # 只有寄件文件展示授权后端,普通文件仍显示站点当前设置。
686+ display_storage_type = await storage_type_for_share (file_code )
692687 detail .update (
693688 {
694689 "filename" : detail ["name" ],
@@ -1417,7 +1412,7 @@ async def download_file(self, file_id: int):
14171412 if file_code .text :
14181413 return APIResponse (detail = file_code .text )
14191414 else :
1420- # 统一处理 NAS 引用路径及普通/寄件文件的存储快照 。
1415+ # NAS 和普通下载沿用上游路径,寄件文件使用其授权后端 。
14211416 return response_from_download (await get_stored_download (file_code , self ._file_storage ))
14221417
14231418 async def preview_file (self , file_id : int , max_chars : int = 4000 ):
@@ -1432,7 +1427,8 @@ async def preview_file(self, file_id: int, max_chars: int = 4000):
14321427 preview = content [:max_chars ]
14331428 return {
14341429 "id" : file_code .id ,
1435- "code" : file_code .code ,
1430+ "code" : "" if file_code .is_private else file_code .code ,
1431+ "is_private" : file_code .is_private ,
14361432 "name" : f"{ file_code .prefix } { file_code .suffix } " ,
14371433 "type" : "text" ,
14381434 "content" : preview ,
@@ -1462,7 +1458,6 @@ async def share_local_file(self, item):
14621458 suffix = suffix ,
14631459 uuid_file_name = local_file .file ,
14641460 file_path = LOCAL_REF_MARKER ,
1465- storage_type = "local" ,
14661461 size = local_file .size or 0 ,
14671462 expired_at = expired_at ,
14681463 expired_count = expired_count ,
@@ -1477,20 +1472,6 @@ async def share_local_file(self, item):
14771472
14781473
14791474class ConfigService :
1480- # 2023 设置页仍使用迁移前的字段名;仅在旧管理接口边界转换,存储保持 snake_case。
1481- LEGACY_CONFIG_FIELDS = {
1482- "errorCount" : "error_count" ,
1483- "errorMinute" : "error_minute" ,
1484- "expireStyle" : "expire_style" ,
1485- "openUpload" : "open_upload" ,
1486- "robotsText" : "robots_text" ,
1487- "showAdminAddr" : "show_admin_addr" ,
1488- "themesChoices" : "themes_choices" ,
1489- "themesSelect" : "themes_select" ,
1490- "uploadCount" : "upload_count" ,
1491- "uploadMinute" : "upload_minute" ,
1492- "uploadSize" : "upload_size" ,
1493- }
14941475 INT_FIELDS = {
14951476 "admin_session_expire" ,
14961477 "enable_chunk" ,
@@ -1514,45 +1495,22 @@ class ConfigService:
15141495 }
15151496 FLOAT_FIELDS = {"opacity" }
15161497
1517- def get_config (self , * , legacy : bool = False ):
1498+ def get_config (self ):
15181499 config = dict (settings .items ())
15191500 config ["admin_token" ] = ""
15201501 for key in INTERNAL_CONFIG_KEYS :
15211502 config .pop (key , None )
1522- if legacy :
1523- # 每个配置只返回一种键名,避免旧页面整表提交时携带两个互相冲突的值。
1524- for old_key , current_key in self .LEGACY_CONFIG_FIELDS .items ():
1525- config [old_key ] = config .pop (current_key )
15261503 return config
15271504
15281505 async def update_config (self , data : dict ):
15291506 current_config = dict (settings .items ())
15301507 next_config = dict (current_config )
1531- # 必须在白名单过滤前转换,否则旧主题保存成功但主题、上传限制等实际未更新。
1532- normalized_data = dict (data )
1533- for old_key , current_key in self .LEGACY_CONFIG_FIELDS .items ():
1534- if old_key not in normalized_data :
1535- continue
1536- value = normalized_data .pop (old_key )
1537- if current_key in normalized_data and normalized_data [current_key ] != value :
1538- raise HTTPException (status_code = 400 , detail = f"{ current_key } 配置值冲突" )
1539- normalized_data [current_key ] = value
15401508 update_data = {
15411509 key : value
1542- for key , value in normalized_data .items ()
1543- if key in settings .default_config
1544- and key not in INTERNAL_CONFIG_KEYS
1545- and key != "themes_choices" # 主题清单由程序维护,兼容旧字段时也不能允许客户端改写。
1510+ for key , value in data .items ()
1511+ if key in settings .default_config and key not in INTERNAL_CONFIG_KEYS
15461512 }
15471513
1548- # 与寄件目录采用相同规则,允许空前缀表示普通上传的默认日期目录。
1549- if "storage_path" in update_data :
1550- from core .path_validation import validate_storage_directory
1551- try :
1552- update_data ["storage_path" ] = validate_storage_directory (update_data ["storage_path" ], allow_empty = True )
1553- except ValueError as exc :
1554- raise HTTPException (422 , str (exc )) from None
1555-
15561514 admin_token = update_data .get ("admin_token" )
15571515 admin_password_changed = False
15581516 if admin_token is None or admin_token == "" :
0 commit comments