tamar-file-hub-client 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
file_hub_client/client.py CHANGED
@@ -157,7 +157,7 @@ class AsyncTamarFileHubClient:
157
157
  self._taple_service = AsyncTapleService(self._client)
158
158
  return self._taple_service
159
159
 
160
- def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None):
160
+ def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None, user_ip: Optional[str] = None):
161
161
  """
162
162
  设置用户上下文信息
163
163
 
@@ -166,8 +166,18 @@ class AsyncTamarFileHubClient:
166
166
  user_id: 用户ID
167
167
  role: 用户角色(默认为 ACCOUNT)
168
168
  actor_id: 操作者ID(如果不同于 user_id)
169
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
169
170
  """
170
- self._client.set_user_context(org_id, user_id, role, actor_id)
171
+ self._client.set_user_context(org_id, user_id, role, actor_id, user_ip)
172
+
173
+ def set_user_ip(self, user_ip: Optional[str]):
174
+ """
175
+ 设置或更新用户IP地址
176
+
177
+ Args:
178
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
179
+ """
180
+ self._client.set_user_ip(user_ip)
171
181
 
172
182
  def get_user_context(self) -> Optional[UserContext]:
173
183
  """获取当前用户上下文"""
@@ -364,7 +374,7 @@ class TamarFileHubClient:
364
374
  self._taple_service = SyncTapleService(self._client)
365
375
  return self._taple_service
366
376
 
367
- def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None):
377
+ def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None, user_ip: Optional[str] = None):
368
378
  """
369
379
  设置用户上下文信息
370
380
 
@@ -373,8 +383,18 @@ class TamarFileHubClient:
373
383
  user_id: 用户ID
374
384
  role: 用户角色(默认为 ACCOUNT)
375
385
  actor_id: 操作者ID(如果不同于 user_id)
386
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
387
+ """
388
+ self._client.set_user_context(org_id, user_id, role, actor_id, user_ip)
389
+
390
+ def set_user_ip(self, user_ip: Optional[str]):
391
+ """
392
+ 设置或更新用户IP地址
393
+
394
+ Args:
395
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
376
396
  """
377
- self._client.set_user_context(org_id, user_id, role, actor_id)
397
+ self._client.set_user_ip(user_ip)
378
398
 
379
399
  def get_user_context(self) -> Optional[UserContext]:
380
400
  """获取当前用户上下文"""
@@ -281,8 +281,17 @@ class AsyncGrpcClient:
281
281
  # 添加默认元数据
282
282
  metadata.update(self.default_metadata)
283
283
 
284
- # 添加/覆盖传入的元数据
285
- metadata.update(kwargs)
284
+ # 自动检测用户真实IP
285
+ from ..utils.ip_detector import get_current_user_ip
286
+ auto_detected_ip = get_current_user_ip()
287
+ if auto_detected_ip and 'x-user-ip' not in metadata:
288
+ # 只有在没有设置过user_ip的情况下才使用自动检测的IP
289
+ metadata['x-user-ip'] = auto_detected_ip
290
+
291
+ # 添加/覆盖传入的元数据,但跳过None值以避免覆盖有效的默认值
292
+ for k, v in kwargs.items():
293
+ if v is not None:
294
+ metadata[k] = v
286
295
 
287
296
  # 处理 request_id(优先级:显式传入 > metadata中的x-request-id > RequestContext > 自动生成)
288
297
  if request_id is not None:
@@ -314,7 +323,7 @@ class AsyncGrpcClient:
314
323
  """
315
324
  self.default_metadata.update(kwargs)
316
325
 
317
- def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None):
326
+ def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None, user_ip: Optional[str] = None):
318
327
  """
319
328
  设置用户上下文信息
320
329
 
@@ -323,16 +332,34 @@ class AsyncGrpcClient:
323
332
  user_id: 用户ID
324
333
  role: 用户角色(默认为 ACCOUNT)
325
334
  actor_id: 操作者ID(如果不同于 user_id)
335
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
326
336
  """
327
337
  self._user_context = UserContext(
328
338
  org_id=org_id,
329
339
  user_id=user_id,
330
340
  role=role,
331
- actor_id=actor_id
341
+ actor_id=actor_id,
342
+ user_ip=user_ip
332
343
  )
333
344
  # 更新到默认元数据
334
345
  self.update_default_metadata(**self._user_context.to_metadata())
335
346
 
347
+ def set_user_ip(self, user_ip: Optional[str]):
348
+ """
349
+ 设置或更新用户IP地址
350
+
351
+ Args:
352
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
353
+ """
354
+ if self._user_context:
355
+ self._user_context.user_ip = user_ip
356
+ # 先移除旧的x-user-ip(如果存在)
357
+ self.default_metadata.pop('x-user-ip', None)
358
+ # 更新到默认元数据(只有非None值会被添加)
359
+ self.update_default_metadata(**self._user_context.to_metadata())
360
+ else:
361
+ raise ValueError("必须先调用 set_user_context 设置用户上下文,然后才能设置用户IP")
362
+
336
363
  def get_user_context(self) -> Optional[UserContext]:
337
364
  """获取当前用户上下文"""
338
365
  return self._user_context
@@ -26,7 +26,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__
26
26
  from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
27
27
 
28
28
 
29
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x66ile_service.proto\x12\x04\x66ile\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xab\x01\n\x04\x46ile\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tfolder_id\x18\x02 \x01(\t\x12\x11\n\tfile_name\x18\x03 \x01(\t\x12\x11\n\tfile_type\x18\x04 \x01(\t\x12.\n\ncreated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb7\x02\n\nUploadFile\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tfolder_id\x18\x02 \x01(\t\x12\x0f\n\x07\x66ile_id\x18\x03 \x01(\t\x12\x14\n\x0cstorage_type\x18\x04 \x01(\t\x12\x13\n\x0bstored_name\x18\x05 \x01(\t\x12\x13\n\x0bstored_path\x18\x06 \x01(\t\x12\x11\n\tfile_name\x18\x07 \x01(\t\x12\x11\n\tfile_size\x18\x08 \x01(\x03\x12\x10\n\x08\x66ile_ext\x18\t \x01(\t\x12\x11\n\tmime_type\x18\n \x01(\t\x12\x0e\n\x06status\x18\x0b \x01(\t\x12.\n\ncreated_at\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\r \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9f\x02\n\x11UploadFileRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\tfile_name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\x12\x11\n\tfile_type\x18\x04 \x01(\t\x12\x11\n\tmime_type\x18\x05 \x01(\t\x12\x19\n\x0cis_temporary\x18\x06 \x01(\x08H\x01\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x07 \x01(\x05H\x02\x88\x01\x01\x12#\n\x16keep_original_filename\x18\x08 \x01(\x08H\x03\x88\x01\x01\x42\x0c\n\n_folder_idB\x0f\n\r_is_temporaryB\x11\n\x0f_expire_secondsB\x19\n\x17_keep_original_filename\"\xb3\x02\n\x10UploadUrlRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\tfile_name\x18\x02 \x01(\t\x12\x11\n\tfile_type\x18\x03 \x01(\t\x12\x11\n\tmime_type\x18\x04 \x01(\t\x12\x11\n\tfile_size\x18\x05 \x01(\x03\x12\x11\n\tfile_hash\x18\x06 \x01(\t\x12\x19\n\x0cis_temporary\x18\x07 \x01(\x08H\x01\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x08 \x01(\x05H\x02\x88\x01\x01\x12#\n\x16keep_original_filename\x18\t \x01(\x08H\x03\x88\x01\x01\x42\x0c\n\n_folder_idB\x0f\n\r_is_temporaryB\x11\n\x0f_expire_secondsB\x19\n\x17_keep_original_filename\")\n\x16UploadCompletedRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"u\n\x12\x44ownloadUrlRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x13\n\x06is_cdn\x18\x03 \x01(\x08H\x00\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\t\n\x07_is_cdnB\x11\n\x0f_expire_seconds\"\xd4\x01\n\x10ShareLinkRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x11\n\tis_public\x18\x02 \x01(\x08\x12\x14\n\x0c\x61\x63\x63\x65ss_scope\x18\x03 \x01(\t\x12\x1b\n\x0e\x65xpire_seconds\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12\x17\n\nmax_access\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x1b\n\x0eshare_password\x18\x06 \x01(\tH\x02\x88\x01\x01\x42\x11\n\x0f_expire_secondsB\r\n\x0b_max_accessB\x11\n\x0f_share_password\"\x82\x01\n\x10\x46ileVisitRequest\x12\x15\n\rfile_share_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65ss_type\x18\x02 \x01(\t\x12\x17\n\x0f\x61\x63\x63\x65ss_duration\x18\x03 \x01(\x05\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"!\n\x0eGetFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"6\n\x11RenameFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x10\n\x08new_name\x18\x02 \x01(\t\"$\n\x11\x44\x65leteFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"\x8d\x02\n\x10ListFilesRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tfile_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x11\n\tfile_type\x18\x03 \x03(\t\x12\x1c\n\x0f\x63reated_by_role\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x17\n\ncreated_by\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x04\x88\x01\x01\x12\x11\n\x04page\x18\x07 \x01(\x05H\x05\x88\x01\x01\x42\x0c\n\n_folder_idB\x0c\n\n_file_nameB\x12\n\x10_created_by_roleB\r\n\x0b_created_byB\x0c\n\n_page_sizeB\x07\n\x05_page\"{\n\x17\x42\x61tchDownloadUrlRequest\x12\x10\n\x08\x66ile_ids\x18\x01 \x03(\t\x12\x13\n\x06is_cdn\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x03 \x01(\x05H\x01\x88\x01\x01\x42\t\n\x07_is_cdnB\x11\n\x0f_expire_seconds\"#\n\x10GetGcsUrlRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\")\n\x15\x42\x61tchGetGcsUrlRequest\x12\x10\n\x08\x66ile_ids\x18\x01 \x03(\t\"U\n\x12UploadFileResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12%\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFile\"a\n\x11UploadUrlResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12%\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFile\x12\x0b\n\x03url\x18\x03 \x01(\t\"\"\n\x13\x44ownloadUrlResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\"*\n\x11ShareLinkResponse\x12\x15\n\rfile_share_id\x18\x01 \x01(\t\"-\n\x10\x46ileListResponse\x12\x19\n\x05\x66iles\x18\x01 \x03(\x0b\x32\n.file.File\"g\n\x0fGetFileResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12*\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFileH\x00\x88\x01\x01\x42\x0e\n\x0c_upload_file\"H\n\x18\x42\x61tchDownloadUrlResponse\x12,\n\rdownload_urls\x18\x01 \x03(\x0b\x32\x15.file.DownloadUrlInfo\"M\n\x0f\x44ownloadUrlInfo\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"7\n\x11GetGcsUrlResponse\x12\x0f\n\x07gcs_url\x18\x01 \x01(\t\x12\x11\n\tmime_type\x18\x02 \x01(\t\"<\n\x16\x42\x61tchGetGcsUrlResponse\x12\"\n\x08gcs_urls\x18\x01 \x03(\x0b\x32\x10.file.GcsUrlInfo\"_\n\nGcsUrlInfo\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x0f\n\x07gcs_url\x18\x02 \x01(\t\x12\x11\n\tmime_type\x18\x03 \x01(\t\x12\x12\n\x05\x65rror\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"\x07\n\x05\x45mpty2\xae\x07\n\x0b\x46ileService\x12?\n\nUploadFile\x12\x17.file.UploadFileRequest\x1a\x18.file.UploadFileResponse\x12\x44\n\x11GenerateUploadUrl\x12\x16.file.UploadUrlRequest\x1a\x17.file.UploadUrlResponse\x12M\n\x1aGenerateResumableUploadUrl\x12\x16.file.UploadUrlRequest\x1a\x17.file.UploadUrlResponse\x12\x43\n\x16\x43onfirmUploadCompleted\x12\x1c.file.UploadCompletedRequest\x1a\x0b.file.Empty\x12J\n\x13GenerateDownloadUrl\x12\x18.file.DownloadUrlRequest\x1a\x19.file.DownloadUrlResponse\x12Y\n\x18\x42\x61tchGenerateDownloadUrl\x12\x1d.file.BatchDownloadUrlRequest\x1a\x1e.file.BatchDownloadUrlResponse\x12<\n\tGetGcsUrl\x12\x16.file.GetGcsUrlRequest\x1a\x17.file.GetGcsUrlResponse\x12K\n\x0e\x42\x61tchGetGcsUrl\x12\x1b.file.BatchGetGcsUrlRequest\x1a\x1c.file.BatchGetGcsUrlResponse\x12\x44\n\x11GenerateShareLink\x12\x16.file.ShareLinkRequest\x1a\x17.file.ShareLinkResponse\x12\x30\n\tVisitFile\x12\x16.file.FileVisitRequest\x1a\x0b.file.Empty\x12\x36\n\x07GetFile\x12\x14.file.GetFileRequest\x1a\x15.file.GetFileResponse\x12\x31\n\nRenameFile\x12\x17.file.RenameFileRequest\x1a\n.file.File\x12\x32\n\nDeleteFile\x12\x17.file.DeleteFileRequest\x1a\x0b.file.Empty\x12;\n\tListFiles\x12\x16.file.ListFilesRequest\x1a\x16.file.FileListResponseb\x06proto3')
29
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x66ile_service.proto\x12\x04\x66ile\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xab\x01\n\x04\x46ile\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tfolder_id\x18\x02 \x01(\t\x12\x11\n\tfile_name\x18\x03 \x01(\t\x12\x11\n\tfile_type\x18\x04 \x01(\t\x12.\n\ncreated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb7\x02\n\nUploadFile\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tfolder_id\x18\x02 \x01(\t\x12\x0f\n\x07\x66ile_id\x18\x03 \x01(\t\x12\x14\n\x0cstorage_type\x18\x04 \x01(\t\x12\x13\n\x0bstored_name\x18\x05 \x01(\t\x12\x13\n\x0bstored_path\x18\x06 \x01(\t\x12\x11\n\tfile_name\x18\x07 \x01(\t\x12\x11\n\tfile_size\x18\x08 \x01(\x03\x12\x10\n\x08\x66ile_ext\x18\t \x01(\t\x12\x11\n\tmime_type\x18\n \x01(\t\x12\x0e\n\x06status\x18\x0b \x01(\t\x12.\n\ncreated_at\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_at\x18\r \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9f\x02\n\x11UploadFileRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\tfile_name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\x12\x11\n\tfile_type\x18\x04 \x01(\t\x12\x11\n\tmime_type\x18\x05 \x01(\t\x12\x19\n\x0cis_temporary\x18\x06 \x01(\x08H\x01\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x07 \x01(\x05H\x02\x88\x01\x01\x12#\n\x16keep_original_filename\x18\x08 \x01(\x08H\x03\x88\x01\x01\x42\x0c\n\n_folder_idB\x0f\n\r_is_temporaryB\x11\n\x0f_expire_secondsB\x19\n\x17_keep_original_filename\"\xb3\x02\n\x10UploadUrlRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x11\n\tfile_name\x18\x02 \x01(\t\x12\x11\n\tfile_type\x18\x03 \x01(\t\x12\x11\n\tmime_type\x18\x04 \x01(\t\x12\x11\n\tfile_size\x18\x05 \x01(\x03\x12\x11\n\tfile_hash\x18\x06 \x01(\t\x12\x19\n\x0cis_temporary\x18\x07 \x01(\x08H\x01\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x08 \x01(\x05H\x02\x88\x01\x01\x12#\n\x16keep_original_filename\x18\t \x01(\x08H\x03\x88\x01\x01\x42\x0c\n\n_folder_idB\x0f\n\r_is_temporaryB\x11\n\x0f_expire_secondsB\x19\n\x17_keep_original_filename\")\n\x16UploadCompletedRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"u\n\x12\x44ownloadUrlRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x13\n\x06is_cdn\x18\x03 \x01(\x08H\x00\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\t\n\x07_is_cdnB\x11\n\x0f_expire_seconds\"\xd4\x01\n\x10ShareLinkRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x11\n\tis_public\x18\x02 \x01(\x08\x12\x14\n\x0c\x61\x63\x63\x65ss_scope\x18\x03 \x01(\t\x12\x1b\n\x0e\x65xpire_seconds\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12\x17\n\nmax_access\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x1b\n\x0eshare_password\x18\x06 \x01(\tH\x02\x88\x01\x01\x42\x11\n\x0f_expire_secondsB\r\n\x0b_max_accessB\x11\n\x0f_share_password\"\x82\x01\n\x10\x46ileVisitRequest\x12\x15\n\rfile_share_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65ss_type\x18\x02 \x01(\t\x12\x17\n\x0f\x61\x63\x63\x65ss_duration\x18\x03 \x01(\x05\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"!\n\x0eGetFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"6\n\x11RenameFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x10\n\x08new_name\x18\x02 \x01(\t\"$\n\x11\x44\x65leteFileRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"\x8d\x02\n\x10ListFilesRequest\x12\x16\n\tfolder_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tfile_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x11\n\tfile_type\x18\x03 \x03(\t\x12\x1c\n\x0f\x63reated_by_role\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x17\n\ncreated_by\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x04\x88\x01\x01\x12\x11\n\x04page\x18\x07 \x01(\x05H\x05\x88\x01\x01\x42\x0c\n\n_folder_idB\x0c\n\n_file_nameB\x12\n\x10_created_by_roleB\r\n\x0b_created_byB\x0c\n\n_page_sizeB\x07\n\x05_page\"{\n\x17\x42\x61tchDownloadUrlRequest\x12\x10\n\x08\x66ile_ids\x18\x01 \x03(\t\x12\x13\n\x06is_cdn\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x1b\n\x0e\x65xpire_seconds\x18\x03 \x01(\x05H\x01\x88\x01\x01\x42\t\n\x07_is_cdnB\x11\n\x0f_expire_seconds\"#\n\x10GetGcsUrlRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\")\n\x15\x42\x61tchGetGcsUrlRequest\x12\x10\n\x08\x66ile_ids\x18\x01 \x03(\t\"U\n\x12UploadFileResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12%\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFile\"a\n\x11UploadUrlResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12%\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFile\x12\x0b\n\x03url\x18\x03 \x01(\t\"\"\n\x13\x44ownloadUrlResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\"*\n\x11ShareLinkResponse\x12\x15\n\rfile_share_id\x18\x01 \x01(\t\"-\n\x10\x46ileListResponse\x12\x19\n\x05\x66iles\x18\x01 \x03(\x0b\x32\n.file.File\"g\n\x0fGetFileResponse\x12\x18\n\x04\x66ile\x18\x01 \x01(\x0b\x32\n.file.File\x12*\n\x0bupload_file\x18\x02 \x01(\x0b\x32\x10.file.UploadFileH\x00\x88\x01\x01\x42\x0e\n\x0c_upload_file\"H\n\x18\x42\x61tchDownloadUrlResponse\x12,\n\rdownload_urls\x18\x01 \x03(\x0b\x32\x15.file.DownloadUrlInfo\"M\n\x0f\x44ownloadUrlInfo\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x12\n\x05\x65rror\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"7\n\x11GetGcsUrlResponse\x12\x0f\n\x07gcs_url\x18\x01 \x01(\t\x12\x11\n\tmime_type\x18\x02 \x01(\t\"<\n\x16\x42\x61tchGetGcsUrlResponse\x12\"\n\x08gcs_urls\x18\x01 \x03(\x0b\x32\x10.file.GcsUrlInfo\"_\n\nGcsUrlInfo\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x0f\n\x07gcs_url\x18\x02 \x01(\t\x12\x11\n\tmime_type\x18\x03 \x01(\t\x12\x12\n\x05\x65rror\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\"+\n\x18\x43ompressionStatusRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\"\x84\x01\n\x19\x43ompressionStatusResponse\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x1a\n\rerror_message\x18\x02 \x01(\tH\x00\x88\x01\x01\x12)\n\x08variants\x18\x03 \x03(\x0b\x32\x17.file.CompressedVariantB\x10\n\x0e_error_message\"Q\n\x12GetVariantsRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x19\n\x0cvariant_type\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0f\n\r_variant_type\"@\n\x13GetVariantsResponse\x12)\n\x08variants\x18\x01 \x03(\x0b\x32\x17.file.CompressedVariant\"\xc7\x02\n\x11\x43ompressedVariant\x12\x14\n\x0cvariant_name\x18\x01 \x01(\t\x12\x14\n\x0cvariant_type\x18\x02 \x01(\t\x12\x12\n\nmedia_type\x18\x03 \x01(\t\x12\r\n\x05width\x18\x04 \x01(\x05\x12\x0e\n\x06height\x18\x05 \x01(\x05\x12\x11\n\tfile_size\x18\x06 \x01(\x03\x12\x0e\n\x06\x66ormat\x18\x07 \x01(\t\x12\x14\n\x07quality\x18\x08 \x01(\x05H\x00\x88\x01\x01\x12\x15\n\x08\x64uration\x18\t \x01(\x01H\x01\x88\x01\x01\x12\x14\n\x07\x62itrate\x18\n \x01(\x03H\x02\x88\x01\x01\x12\x10\n\x03\x66ps\x18\x0b \x01(\x05H\x03\x88\x01\x01\x12\x19\n\x11\x63ompression_ratio\x18\x0c \x01(\x01\x12\x13\n\x0bstored_path\x18\r \x01(\tB\n\n\x08_qualityB\x0b\n\t_durationB\n\n\x08_bitrateB\x06\n\x04_fps\"Y\n\x14RecompressionRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x1c\n\x0f\x66orce_reprocess\x18\x02 \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_force_reprocess\"8\n\x15RecompressionResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\t\"\x92\x01\n\x19VariantDownloadUrlRequest\x12\x0f\n\x07\x66ile_id\x18\x01 \x01(\t\x12\x14\n\x0cvariant_name\x18\x02 \x01(\t\x12\x1b\n\x0e\x65xpire_seconds\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x13\n\x06is_cdn\x18\x04 \x01(\x08H\x01\x88\x01\x01\x42\x11\n\x0f_expire_secondsB\t\n\x07_is_cdn\"\x8c\x01\n\x1aVariantDownloadUrlResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\x12\n\x05\x65rror\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x32\n\x0cvariant_info\x18\x03 \x01(\x0b\x32\x17.file.CompressedVariantH\x01\x88\x01\x01\x42\x08\n\x06_errorB\x0f\n\r_variant_info\"\x07\n\x05\x45mpty2\x87\n\n\x0b\x46ileService\x12?\n\nUploadFile\x12\x17.file.UploadFileRequest\x1a\x18.file.UploadFileResponse\x12\x44\n\x11GenerateUploadUrl\x12\x16.file.UploadUrlRequest\x1a\x17.file.UploadUrlResponse\x12M\n\x1aGenerateResumableUploadUrl\x12\x16.file.UploadUrlRequest\x1a\x17.file.UploadUrlResponse\x12\x43\n\x16\x43onfirmUploadCompleted\x12\x1c.file.UploadCompletedRequest\x1a\x0b.file.Empty\x12J\n\x13GenerateDownloadUrl\x12\x18.file.DownloadUrlRequest\x1a\x19.file.DownloadUrlResponse\x12Y\n\x18\x42\x61tchGenerateDownloadUrl\x12\x1d.file.BatchDownloadUrlRequest\x1a\x1e.file.BatchDownloadUrlResponse\x12<\n\tGetGcsUrl\x12\x16.file.GetGcsUrlRequest\x1a\x17.file.GetGcsUrlResponse\x12K\n\x0e\x42\x61tchGetGcsUrl\x12\x1b.file.BatchGetGcsUrlRequest\x1a\x1c.file.BatchGetGcsUrlResponse\x12\x44\n\x11GenerateShareLink\x12\x16.file.ShareLinkRequest\x1a\x17.file.ShareLinkResponse\x12\x30\n\tVisitFile\x12\x16.file.FileVisitRequest\x1a\x0b.file.Empty\x12\x36\n\x07GetFile\x12\x14.file.GetFileRequest\x1a\x15.file.GetFileResponse\x12\x31\n\nRenameFile\x12\x17.file.RenameFileRequest\x1a\n.file.File\x12\x32\n\nDeleteFile\x12\x17.file.DeleteFileRequest\x1a\x0b.file.Empty\x12;\n\tListFiles\x12\x16.file.ListFilesRequest\x1a\x16.file.FileListResponse\x12W\n\x14GetCompressionStatus\x12\x1e.file.CompressionStatusRequest\x1a\x1f.file.CompressionStatusResponse\x12L\n\x15GetCompressedVariants\x12\x18.file.GetVariantsRequest\x1a\x19.file.GetVariantsResponse\x12O\n\x14TriggerRecompression\x12\x1a.file.RecompressionRequest\x1a\x1b.file.RecompressionResponse\x12_\n\x1aGenerateVariantDownloadUrl\x12\x1f.file.VariantDownloadUrlRequest\x1a .file.VariantDownloadUrlResponseb\x06proto3')
30
30
 
31
31
  _globals = globals()
32
32
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -85,8 +85,26 @@ if not _descriptor._USE_C_DESCRIPTORS:
85
85
  _globals['_BATCHGETGCSURLRESPONSE']._serialized_end=2983
86
86
  _globals['_GCSURLINFO']._serialized_start=2985
87
87
  _globals['_GCSURLINFO']._serialized_end=3080
88
- _globals['_EMPTY']._serialized_start=3082
89
- _globals['_EMPTY']._serialized_end=3089
90
- _globals['_FILESERVICE']._serialized_start=3092
91
- _globals['_FILESERVICE']._serialized_end=4034
88
+ _globals['_COMPRESSIONSTATUSREQUEST']._serialized_start=3082
89
+ _globals['_COMPRESSIONSTATUSREQUEST']._serialized_end=3125
90
+ _globals['_COMPRESSIONSTATUSRESPONSE']._serialized_start=3128
91
+ _globals['_COMPRESSIONSTATUSRESPONSE']._serialized_end=3260
92
+ _globals['_GETVARIANTSREQUEST']._serialized_start=3262
93
+ _globals['_GETVARIANTSREQUEST']._serialized_end=3343
94
+ _globals['_GETVARIANTSRESPONSE']._serialized_start=3345
95
+ _globals['_GETVARIANTSRESPONSE']._serialized_end=3409
96
+ _globals['_COMPRESSEDVARIANT']._serialized_start=3412
97
+ _globals['_COMPRESSEDVARIANT']._serialized_end=3739
98
+ _globals['_RECOMPRESSIONREQUEST']._serialized_start=3741
99
+ _globals['_RECOMPRESSIONREQUEST']._serialized_end=3830
100
+ _globals['_RECOMPRESSIONRESPONSE']._serialized_start=3832
101
+ _globals['_RECOMPRESSIONRESPONSE']._serialized_end=3888
102
+ _globals['_VARIANTDOWNLOADURLREQUEST']._serialized_start=3891
103
+ _globals['_VARIANTDOWNLOADURLREQUEST']._serialized_end=4037
104
+ _globals['_VARIANTDOWNLOADURLRESPONSE']._serialized_start=4040
105
+ _globals['_VARIANTDOWNLOADURLRESPONSE']._serialized_end=4180
106
+ _globals['_EMPTY']._serialized_start=4182
107
+ _globals['_EMPTY']._serialized_end=4189
108
+ _globals['_FILESERVICE']._serialized_start=4192
109
+ _globals['_FILESERVICE']._serialized_end=5479
92
110
  # @@protoc_insertion_point(module_scope)
@@ -106,6 +106,26 @@ class FileServiceStub(object):
106
106
  request_serializer=file__service__pb2.ListFilesRequest.SerializeToString,
107
107
  response_deserializer=file__service__pb2.FileListResponse.FromString,
108
108
  _registered_method=True)
109
+ self.GetCompressionStatus = channel.unary_unary(
110
+ '/file.FileService/GetCompressionStatus',
111
+ request_serializer=file__service__pb2.CompressionStatusRequest.SerializeToString,
112
+ response_deserializer=file__service__pb2.CompressionStatusResponse.FromString,
113
+ _registered_method=True)
114
+ self.GetCompressedVariants = channel.unary_unary(
115
+ '/file.FileService/GetCompressedVariants',
116
+ request_serializer=file__service__pb2.GetVariantsRequest.SerializeToString,
117
+ response_deserializer=file__service__pb2.GetVariantsResponse.FromString,
118
+ _registered_method=True)
119
+ self.TriggerRecompression = channel.unary_unary(
120
+ '/file.FileService/TriggerRecompression',
121
+ request_serializer=file__service__pb2.RecompressionRequest.SerializeToString,
122
+ response_deserializer=file__service__pb2.RecompressionResponse.FromString,
123
+ _registered_method=True)
124
+ self.GenerateVariantDownloadUrl = channel.unary_unary(
125
+ '/file.FileService/GenerateVariantDownloadUrl',
126
+ request_serializer=file__service__pb2.VariantDownloadUrlRequest.SerializeToString,
127
+ response_deserializer=file__service__pb2.VariantDownloadUrlResponse.FromString,
128
+ _registered_method=True)
109
129
 
110
130
 
111
131
  class FileServiceServicer(object):
@@ -197,6 +217,31 @@ class FileServiceServicer(object):
197
217
  context.set_details('Method not implemented!')
198
218
  raise NotImplementedError('Method not implemented!')
199
219
 
220
+ def GetCompressionStatus(self, request, context):
221
+ """压缩服务相关API
222
+ """
223
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
224
+ context.set_details('Method not implemented!')
225
+ raise NotImplementedError('Method not implemented!')
226
+
227
+ def GetCompressedVariants(self, request, context):
228
+ """Missing associated documentation comment in .proto file."""
229
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
230
+ context.set_details('Method not implemented!')
231
+ raise NotImplementedError('Method not implemented!')
232
+
233
+ def TriggerRecompression(self, request, context):
234
+ """Missing associated documentation comment in .proto file."""
235
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
236
+ context.set_details('Method not implemented!')
237
+ raise NotImplementedError('Method not implemented!')
238
+
239
+ def GenerateVariantDownloadUrl(self, request, context):
240
+ """Missing associated documentation comment in .proto file."""
241
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
242
+ context.set_details('Method not implemented!')
243
+ raise NotImplementedError('Method not implemented!')
244
+
200
245
 
201
246
  def add_FileServiceServicer_to_server(servicer, server):
202
247
  rpc_method_handlers = {
@@ -270,6 +315,26 @@ def add_FileServiceServicer_to_server(servicer, server):
270
315
  request_deserializer=file__service__pb2.ListFilesRequest.FromString,
271
316
  response_serializer=file__service__pb2.FileListResponse.SerializeToString,
272
317
  ),
318
+ 'GetCompressionStatus': grpc.unary_unary_rpc_method_handler(
319
+ servicer.GetCompressionStatus,
320
+ request_deserializer=file__service__pb2.CompressionStatusRequest.FromString,
321
+ response_serializer=file__service__pb2.CompressionStatusResponse.SerializeToString,
322
+ ),
323
+ 'GetCompressedVariants': grpc.unary_unary_rpc_method_handler(
324
+ servicer.GetCompressedVariants,
325
+ request_deserializer=file__service__pb2.GetVariantsRequest.FromString,
326
+ response_serializer=file__service__pb2.GetVariantsResponse.SerializeToString,
327
+ ),
328
+ 'TriggerRecompression': grpc.unary_unary_rpc_method_handler(
329
+ servicer.TriggerRecompression,
330
+ request_deserializer=file__service__pb2.RecompressionRequest.FromString,
331
+ response_serializer=file__service__pb2.RecompressionResponse.SerializeToString,
332
+ ),
333
+ 'GenerateVariantDownloadUrl': grpc.unary_unary_rpc_method_handler(
334
+ servicer.GenerateVariantDownloadUrl,
335
+ request_deserializer=file__service__pb2.VariantDownloadUrlRequest.FromString,
336
+ response_serializer=file__service__pb2.VariantDownloadUrlResponse.SerializeToString,
337
+ ),
273
338
  }
274
339
  generic_handler = grpc.method_handlers_generic_handler(
275
340
  'file.FileService', rpc_method_handlers)
@@ -660,3 +725,111 @@ class FileService(object):
660
725
  timeout,
661
726
  metadata,
662
727
  _registered_method=True)
728
+
729
+ @staticmethod
730
+ def GetCompressionStatus(request,
731
+ target,
732
+ options=(),
733
+ channel_credentials=None,
734
+ call_credentials=None,
735
+ insecure=False,
736
+ compression=None,
737
+ wait_for_ready=None,
738
+ timeout=None,
739
+ metadata=None):
740
+ return grpc.experimental.unary_unary(
741
+ request,
742
+ target,
743
+ '/file.FileService/GetCompressionStatus',
744
+ file__service__pb2.CompressionStatusRequest.SerializeToString,
745
+ file__service__pb2.CompressionStatusResponse.FromString,
746
+ options,
747
+ channel_credentials,
748
+ insecure,
749
+ call_credentials,
750
+ compression,
751
+ wait_for_ready,
752
+ timeout,
753
+ metadata,
754
+ _registered_method=True)
755
+
756
+ @staticmethod
757
+ def GetCompressedVariants(request,
758
+ target,
759
+ options=(),
760
+ channel_credentials=None,
761
+ call_credentials=None,
762
+ insecure=False,
763
+ compression=None,
764
+ wait_for_ready=None,
765
+ timeout=None,
766
+ metadata=None):
767
+ return grpc.experimental.unary_unary(
768
+ request,
769
+ target,
770
+ '/file.FileService/GetCompressedVariants',
771
+ file__service__pb2.GetVariantsRequest.SerializeToString,
772
+ file__service__pb2.GetVariantsResponse.FromString,
773
+ options,
774
+ channel_credentials,
775
+ insecure,
776
+ call_credentials,
777
+ compression,
778
+ wait_for_ready,
779
+ timeout,
780
+ metadata,
781
+ _registered_method=True)
782
+
783
+ @staticmethod
784
+ def TriggerRecompression(request,
785
+ target,
786
+ options=(),
787
+ channel_credentials=None,
788
+ call_credentials=None,
789
+ insecure=False,
790
+ compression=None,
791
+ wait_for_ready=None,
792
+ timeout=None,
793
+ metadata=None):
794
+ return grpc.experimental.unary_unary(
795
+ request,
796
+ target,
797
+ '/file.FileService/TriggerRecompression',
798
+ file__service__pb2.RecompressionRequest.SerializeToString,
799
+ file__service__pb2.RecompressionResponse.FromString,
800
+ options,
801
+ channel_credentials,
802
+ insecure,
803
+ call_credentials,
804
+ compression,
805
+ wait_for_ready,
806
+ timeout,
807
+ metadata,
808
+ _registered_method=True)
809
+
810
+ @staticmethod
811
+ def GenerateVariantDownloadUrl(request,
812
+ target,
813
+ options=(),
814
+ channel_credentials=None,
815
+ call_credentials=None,
816
+ insecure=False,
817
+ compression=None,
818
+ wait_for_ready=None,
819
+ timeout=None,
820
+ metadata=None):
821
+ return grpc.experimental.unary_unary(
822
+ request,
823
+ target,
824
+ '/file.FileService/GenerateVariantDownloadUrl',
825
+ file__service__pb2.VariantDownloadUrlRequest.SerializeToString,
826
+ file__service__pb2.VariantDownloadUrlResponse.FromString,
827
+ options,
828
+ channel_credentials,
829
+ insecure,
830
+ call_credentials,
831
+ compression,
832
+ wait_for_ready,
833
+ timeout,
834
+ metadata,
835
+ _registered_method=True)
@@ -22,6 +22,12 @@ service FileService {
22
22
  rpc RenameFile (RenameFileRequest) returns (File);
23
23
  rpc DeleteFile (DeleteFileRequest) returns (Empty);
24
24
  rpc ListFiles (ListFilesRequest) returns (FileListResponse);
25
+
26
+ // 压缩服务相关API
27
+ rpc GetCompressionStatus (CompressionStatusRequest) returns (CompressionStatusResponse);
28
+ rpc GetCompressedVariants (GetVariantsRequest) returns (GetVariantsResponse);
29
+ rpc TriggerRecompression (RecompressionRequest) returns (RecompressionResponse);
30
+ rpc GenerateVariantDownloadUrl (VariantDownloadUrlRequest) returns (VariantDownloadUrlResponse);
25
31
  }
26
32
 
27
33
  // ========= 数据结构定义 =========
@@ -195,4 +201,64 @@ message GcsUrlInfo {
195
201
  optional string error = 4;
196
202
  }
197
203
 
204
+ // ========= 压缩服务相关结构 =========
205
+
206
+ message CompressionStatusRequest {
207
+ string file_id = 1;
208
+ }
209
+
210
+ message CompressionStatusResponse {
211
+ string status = 1; // pending, processing, completed, failed
212
+ optional string error_message = 2;
213
+ repeated CompressedVariant variants = 3;
214
+ }
215
+
216
+ message GetVariantsRequest {
217
+ string file_id = 1;
218
+ optional string variant_type = 2; // image, video, thumbnail
219
+ }
220
+
221
+ message GetVariantsResponse {
222
+ repeated CompressedVariant variants = 1;
223
+ }
224
+
225
+ message CompressedVariant {
226
+ string variant_name = 1;
227
+ string variant_type = 2;
228
+ string media_type = 3;
229
+ int32 width = 4;
230
+ int32 height = 5;
231
+ int64 file_size = 6;
232
+ string format = 7;
233
+ optional int32 quality = 8;
234
+ optional double duration = 9;
235
+ optional int64 bitrate = 10;
236
+ optional int32 fps = 11;
237
+ double compression_ratio = 12;
238
+ string stored_path = 13;
239
+ }
240
+
241
+ message RecompressionRequest {
242
+ string file_id = 1;
243
+ optional bool force_reprocess = 2;
244
+ }
245
+
246
+ message RecompressionResponse {
247
+ string task_id = 1;
248
+ string status = 2;
249
+ }
250
+
251
+ message VariantDownloadUrlRequest {
252
+ string file_id = 1;
253
+ string variant_name = 2; // large/medium/small/thumbnail
254
+ optional int32 expire_seconds = 3;
255
+ optional bool is_cdn = 4;
256
+ }
257
+
258
+ message VariantDownloadUrlResponse {
259
+ string url = 1;
260
+ optional string error = 2;
261
+ optional CompressedVariant variant_info = 3; // 返回变体详细信息
262
+ }
263
+
198
264
  message Empty {}
@@ -284,8 +284,17 @@ class SyncGrpcClient:
284
284
  # 添加默认元数据
285
285
  metadata.update(self.default_metadata)
286
286
 
287
- # 添加/覆盖传入的元数据
288
- metadata.update(kwargs)
287
+ # 自动检测用户真实IP
288
+ from ..utils.ip_detector import get_current_user_ip
289
+ auto_detected_ip = get_current_user_ip()
290
+ if auto_detected_ip and 'x-user-ip' not in metadata:
291
+ # 只有在没有设置过user_ip的情况下才使用自动检测的IP
292
+ metadata['x-user-ip'] = auto_detected_ip
293
+
294
+ # 添加/覆盖传入的元数据,但跳过None值以避免覆盖有效的默认值
295
+ for k, v in kwargs.items():
296
+ if v is not None:
297
+ metadata[k] = v
289
298
 
290
299
  # 处理 request_id(优先级:显式传入 > metadata中的x-request-id > RequestContext > 自动生成)
291
300
  if request_id is not None:
@@ -317,7 +326,7 @@ class SyncGrpcClient:
317
326
  """
318
327
  self.default_metadata.update(kwargs)
319
328
 
320
- def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None):
329
+ def set_user_context(self, org_id: str, user_id: str, role: Role = Role.ACCOUNT, actor_id: Optional[str] = None, user_ip: Optional[str] = None):
321
330
  """
322
331
  设置用户上下文信息
323
332
 
@@ -326,16 +335,34 @@ class SyncGrpcClient:
326
335
  user_id: 用户ID
327
336
  role: 用户角色(默认为 ACCOUNT)
328
337
  actor_id: 操作者ID(如果不同于 user_id)
338
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
329
339
  """
330
340
  self._user_context = UserContext(
331
341
  org_id=org_id,
332
342
  user_id=user_id,
333
343
  role=role,
334
- actor_id=actor_id
344
+ actor_id=actor_id,
345
+ user_ip=user_ip
335
346
  )
336
347
  # 更新到默认元数据
337
348
  self.update_default_metadata(**self._user_context.to_metadata())
338
349
 
350
+ def set_user_ip(self, user_ip: Optional[str]):
351
+ """
352
+ 设置或更新用户IP地址
353
+
354
+ Args:
355
+ user_ip: 用户IP地址(实际请求用户的IP,如前端用户的IP)
356
+ """
357
+ if self._user_context:
358
+ self._user_context.user_ip = user_ip
359
+ # 先移除旧的x-user-ip(如果存在)
360
+ self.default_metadata.pop('x-user-ip', None)
361
+ # 更新到默认元数据(只有非None值会被添加)
362
+ self.update_default_metadata(**self._user_context.to_metadata())
363
+ else:
364
+ raise ValueError("必须先调用 set_user_context 设置用户上下文,然后才能设置用户IP")
365
+
339
366
  def get_user_context(self) -> Optional[UserContext]:
340
367
  """获取当前用户上下文"""
341
368
  return self._user_context
@@ -16,6 +16,11 @@ from .file import (
16
16
  GcsUrlInfo,
17
17
  GetGcsUrlResponse,
18
18
  BatchGcsUrlResponse,
19
+ CompressedVariant,
20
+ CompressionStatusResponse,
21
+ GetVariantsResponse,
22
+ RecompressionResponse,
23
+ VariantDownloadUrlResponse,
19
24
  )
20
25
  from .folder import (
21
26
  FolderInfo,
@@ -73,6 +78,11 @@ __all__ = [
73
78
  "GcsUrlInfo",
74
79
  "GetGcsUrlResponse",
75
80
  "BatchGcsUrlResponse",
81
+ "CompressedVariant",
82
+ "CompressionStatusResponse",
83
+ "GetVariantsResponse",
84
+ "RecompressionResponse",
85
+ "VariantDownloadUrlResponse",
76
86
 
77
87
  # 文件夹相关
78
88
  "FolderInfo",