2025-03-24 06:34:03 +00:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
# coding:utf-8
|
|
|
|
|
|
import json
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
import config
|
|
|
|
|
|
import utils
|
|
|
|
|
|
|
|
|
|
|
|
class CDNConfig:
|
|
|
|
|
|
enable = False
|
|
|
|
|
|
dir = ''
|
|
|
|
|
|
filename = ''
|
|
|
|
|
|
version = ''
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, enable: bool, dir: str, filename: str, version: str):
|
|
|
|
|
|
self.enable = enable
|
|
|
|
|
|
self.dir = dir
|
|
|
|
|
|
self.filename = filename
|
|
|
|
|
|
self.version = version
|
|
|
|
|
|
|
|
|
|
|
|
class RemoteConfig:
|
|
|
|
|
|
enable = False
|
|
|
|
|
|
key = ''
|
|
|
|
|
|
group = ''
|
|
|
|
|
|
condition = ''
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, enable, key, group, condition):
|
|
|
|
|
|
self.enable = enable
|
|
|
|
|
|
self.key = key
|
|
|
|
|
|
self.group = group
|
|
|
|
|
|
self.condition = condition
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigGenerator:
|
|
|
|
|
|
google_sheet_file_name = None
|
|
|
|
|
|
sheet_table_name = None
|
|
|
|
|
|
project = None
|
|
|
|
|
|
platform = None
|
|
|
|
|
|
env = None
|
|
|
|
|
|
cdn_config = None
|
|
|
|
|
|
remote_config = None
|
|
|
|
|
|
sheet_helper = None
|
|
|
|
|
|
firebase_helper = None
|
|
|
|
|
|
|
|
|
|
|
|
field_dict = {} # 字段名称字典
|
|
|
|
|
|
field_type_dict = {} # 字段类型字典
|
|
|
|
|
|
|
|
|
|
|
|
# region 配置生成器初始化
|
|
|
|
|
|
def __init__(self, google_sheet_file_name, sheet_table_name, project, platform, env):
|
|
|
|
|
|
self.sheet_helper = config.sheet_helper
|
|
|
|
|
|
self.google_sheet_file_name = google_sheet_file_name
|
|
|
|
|
|
self.sheet_table_name = sheet_table_name
|
|
|
|
|
|
self.project = project
|
|
|
|
|
|
self.platform = platform.lower()
|
|
|
|
|
|
self.env = env
|
|
|
|
|
|
self.init_notification_param()
|
|
|
|
|
|
self.init_config_generator_param()
|
|
|
|
|
|
|
|
|
|
|
|
def init_notification_param(self):
|
|
|
|
|
|
config.notification.append_msg(f'生成配置文件[文件名: {self.google_sheet_file_name}, 表名: {self.sheet_table_name}]')
|
|
|
|
|
|
config.notification.append_msg(f'配置构建参数[项目: {self.project}, 平台: {self.platform}, 环境: {self.env}]')
|
|
|
|
|
|
|
|
|
|
|
|
def init_config_generator_param(self):
|
|
|
|
|
|
sheet = self.sheet_helper.get_sheet_table(self.google_sheet_file_name, self.sheet_table_name)
|
|
|
|
|
|
if sheet is None:
|
|
|
|
|
|
config.wechat_alert_exception(f'获取表格失败[文件名: {self.google_sheet_file_name}, 表名: {self.sheet_table_name}]')
|
|
|
|
|
|
sheet_all_row_datas = sheet.get_all_values(major_dimension='ROWS')
|
|
|
|
|
|
for i, row_values in enumerate(sheet_all_row_datas):
|
|
|
|
|
|
if i == 0:
|
|
|
|
|
|
self.parse_cdn_config(row_values)
|
|
|
|
|
|
elif i == 1:
|
|
|
|
|
|
self.parse_remote_config(row_values)
|
2025-03-24 07:32:13 +00:00
|
|
|
|
elif i == 2:
|
|
|
|
|
|
continue
|
2025-03-24 06:34:03 +00:00
|
|
|
|
elif i == 3:
|
|
|
|
|
|
self.parse_field(row_values, self.field_dict)
|
|
|
|
|
|
elif i == 4:
|
|
|
|
|
|
self.parse_field_type(row_values, self.field_type_dict)
|
|
|
|
|
|
else:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
def parse_cdn_config(self, row_values):
|
|
|
|
|
|
enable_index = -1
|
|
|
|
|
|
enable_value = None
|
|
|
|
|
|
dir_index = -1
|
|
|
|
|
|
dir_value = None
|
|
|
|
|
|
filename_index = -1
|
|
|
|
|
|
filename_value = None
|
|
|
|
|
|
version_index = -1
|
|
|
|
|
|
version_value = None
|
|
|
|
|
|
|
|
|
|
|
|
for i, cell_value in enumerate(row_values):
|
|
|
|
|
|
if cell_value == 'enable':
|
|
|
|
|
|
enable_index = i + 1
|
|
|
|
|
|
elif cell_value == '目录':
|
|
|
|
|
|
dir_index = i + 1
|
|
|
|
|
|
elif cell_value == '文件名':
|
|
|
|
|
|
filename_index = i + 1
|
|
|
|
|
|
elif cell_value == '版本':
|
|
|
|
|
|
version_index = i + 1
|
|
|
|
|
|
|
|
|
|
|
|
for i, cell_value in enumerate(row_values):
|
|
|
|
|
|
if i == enable_index:
|
|
|
|
|
|
if str(cell_value).lower() == 'true':
|
|
|
|
|
|
enable_value = True
|
|
|
|
|
|
elif i == dir_index:
|
|
|
|
|
|
dir_value = cell_value
|
|
|
|
|
|
elif i == filename_index:
|
|
|
|
|
|
filename_value = cell_value
|
|
|
|
|
|
elif i == version_index:
|
|
|
|
|
|
version_value = cell_value
|
|
|
|
|
|
self.cdn_config = CDNConfig(enable_value, dir_value, filename_value, version_value)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_remote_config(self, row_values):
|
|
|
|
|
|
pass
|
|
|
|
|
|
enable_index = -1
|
|
|
|
|
|
enable_value = False
|
|
|
|
|
|
key_index = -1
|
|
|
|
|
|
key_value = None
|
|
|
|
|
|
group_index = -1
|
|
|
|
|
|
group_value = None
|
|
|
|
|
|
condition_index = -1
|
|
|
|
|
|
condition_value = None
|
|
|
|
|
|
for i, cell_value in enumerate(row_values):
|
|
|
|
|
|
if cell_value == 'enable':
|
|
|
|
|
|
enable_index = i + 1
|
|
|
|
|
|
if cell_value == 'key':
|
|
|
|
|
|
key_index = i + 1
|
|
|
|
|
|
elif cell_value == 'group':
|
|
|
|
|
|
group_index = i + 1
|
|
|
|
|
|
elif cell_value == 'condition':
|
|
|
|
|
|
condition_index = i + 1
|
|
|
|
|
|
for i, cell_value in enumerate(row_values):
|
|
|
|
|
|
if i == enable_index:
|
|
|
|
|
|
if str(cell_value).lower() == 'true':
|
|
|
|
|
|
enable_value = True
|
|
|
|
|
|
elif i == key_index:
|
|
|
|
|
|
remote_key = cell_value
|
|
|
|
|
|
if '#platform#' in remote_key:
|
|
|
|
|
|
remote_key = remote_key.replace('#platform#', self.platform)
|
|
|
|
|
|
key_value = remote_key
|
|
|
|
|
|
elif i == group_index:
|
|
|
|
|
|
group_value = cell_value
|
|
|
|
|
|
elif i == condition_index:
|
|
|
|
|
|
condition_value = cell_value
|
|
|
|
|
|
self.remote_config = RemoteConfig(enable_value, key_value, group_value, condition_value)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_field(self, row_values, field_dict):
|
|
|
|
|
|
for i, field_name in enumerate(row_values):
|
|
|
|
|
|
if i == 0:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if field_name != '':
|
|
|
|
|
|
field_dict[i] = field_name
|
|
|
|
|
|
print(str(field_dict))
|
|
|
|
|
|
|
|
|
|
|
|
def parse_field_type(self, row_values, field_type_dict):
|
|
|
|
|
|
for i, field_type in enumerate(row_values):
|
|
|
|
|
|
if i == 0:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if field_type != '':
|
|
|
|
|
|
field_type_dict[i] = field_type
|
|
|
|
|
|
print(str(field_type_dict))
|
|
|
|
|
|
# endregion
|
|
|
|
|
|
|
|
|
|
|
|
def gen_config_json(self):
|
|
|
|
|
|
sheet = self.sheet_helper.get_sheet_table(self.google_sheet_file_name, self.sheet_table_name)
|
|
|
|
|
|
sheet_all_row_datas = sheet.get_all_values(major_dimension='ROWS')
|
|
|
|
|
|
config_json = {'datas': []}
|
|
|
|
|
|
error_lines = []
|
|
|
|
|
|
data_row_index = -1
|
|
|
|
|
|
for i, row_values in enumerate(sheet_all_row_datas):
|
|
|
|
|
|
if data_row_index == -1:
|
|
|
|
|
|
if 'data' not in str(row_values[0]).lower():
|
|
|
|
|
|
continue
|
|
|
|
|
|
else:
|
|
|
|
|
|
data_row_index = i
|
|
|
|
|
|
|
|
|
|
|
|
if self.is_row_env_valid(row_values) is False:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
item_data, error = self.parse_row_data(i + 1, row_values, self.field_dict, self.field_type_dict)
|
|
|
|
|
|
if error != '':
|
|
|
|
|
|
error_lines.append(error)
|
|
|
|
|
|
elif len(item_data) == 0:
|
|
|
|
|
|
continue
|
|
|
|
|
|
else:
|
|
|
|
|
|
config_json['datas'].append(item_data)
|
|
|
|
|
|
|
|
|
|
|
|
if len(error_lines) > 0:
|
|
|
|
|
|
config.notification.append_msg(str(error_lines))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
_json = json.dumps(config_json)
|
|
|
|
|
|
filename = self.get_config_filename()
|
|
|
|
|
|
local_file_path = self.get_local_config_file_path()
|
|
|
|
|
|
utils.write_json_file(local_file_path, _json)
|
|
|
|
|
|
config.notification.append_msg(f"{filename} 关卡配置生成成功!当前总关卡数:{len(config_json['datas'])}")
|
2025-03-24 07:32:13 +00:00
|
|
|
|
# config.wechat_alert_message(f'{filename}:{_json}')
|
|
|
|
|
|
print(f"{filename}:{_json}")
|
2025-03-24 06:34:03 +00:00
|
|
|
|
return _json
|
|
|
|
|
|
|
|
|
|
|
|
# region 行数据解析
|
|
|
|
|
|
def is_row_env_valid(self, row_values):
|
|
|
|
|
|
if self.env == config.env.debug.value:
|
|
|
|
|
|
return True
|
|
|
|
|
|
if row_values[1] == 'TRUE':
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def parse_row_data(self, row, row_values, field_dict, field_type_dict):
|
|
|
|
|
|
item_data = {}
|
|
|
|
|
|
error = ''
|
|
|
|
|
|
for i, value in enumerate(row_values):
|
|
|
|
|
|
if i not in field_dict or i not in field_type_dict:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
field = field_dict[i]
|
|
|
|
|
|
field_type = field_type_dict[i]
|
|
|
|
|
|
isvalid, check_error = self.check_field_type_valid(field_type, value)
|
|
|
|
|
|
if isvalid:
|
|
|
|
|
|
item_data[field] = self.get_type_value(field_type, value)
|
|
|
|
|
|
else:
|
|
|
|
|
|
error += f'|{check_error}'
|
|
|
|
|
|
if error != '':
|
|
|
|
|
|
error = f"第{row}行->id:{item_data['id']}关卡配置错误: {error}"
|
|
|
|
|
|
return item_data, error
|
|
|
|
|
|
|
|
|
|
|
|
def check_field_type_valid(self, field_type, value):
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
return False, f'值为空'
|
|
|
|
|
|
|
2025-03-24 07:32:13 +00:00
|
|
|
|
if value == '': # 默认值
|
2025-03-24 06:34:03 +00:00
|
|
|
|
return True, ''
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
if field_type == 'int':
|
|
|
|
|
|
int(value)
|
|
|
|
|
|
elif field_type == 'float' or field_type == 'double':
|
|
|
|
|
|
float(value)
|
|
|
|
|
|
elif field_type == 'bool':
|
|
|
|
|
|
if value.lower() not in ['true', 'false']:
|
|
|
|
|
|
return False, f'布尔值格式错误: {value}'
|
|
|
|
|
|
elif field_type == 'string':
|
|
|
|
|
|
# 字符串类型不需要特殊验证
|
|
|
|
|
|
pass
|
|
|
|
|
|
elif field_type.startswith('List<'):
|
|
|
|
|
|
inner_type = field_type[5:-1] # 提取 List<type> 中的 type
|
|
|
|
|
|
if value == '':
|
|
|
|
|
|
return True, '' # 空列表是有效的
|
|
|
|
|
|
|
|
|
|
|
|
items = value.split('#')
|
|
|
|
|
|
for item in items:
|
|
|
|
|
|
if item == '':
|
|
|
|
|
|
continue
|
|
|
|
|
|
valid, error = self.check_field_type_valid(inner_type, item)
|
|
|
|
|
|
if not valid:
|
|
|
|
|
|
return False, f'列表元素 {item} {error}'
|
|
|
|
|
|
else:
|
|
|
|
|
|
return False, f'未知类型: {field_type}'
|
|
|
|
|
|
|
|
|
|
|
|
return True, ''
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return False, f'类型转换错误: 无法将 {value} 转换为 {field_type}'
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return False, f'验证错误: {str(e)}'
|
|
|
|
|
|
|
|
|
|
|
|
def get_type_value(self, field_type, value):
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
if field_type == 'int':
|
|
|
|
|
|
if value == '':
|
|
|
|
|
|
return 0
|
|
|
|
|
|
return int(value)
|
|
|
|
|
|
elif field_type == 'float' or field_type == 'double':
|
|
|
|
|
|
if value == '':
|
|
|
|
|
|
return 0.0
|
|
|
|
|
|
return float(value)
|
|
|
|
|
|
elif field_type == 'bool':
|
|
|
|
|
|
if value == '':
|
|
|
|
|
|
return False
|
|
|
|
|
|
return value.lower() == 'true'
|
|
|
|
|
|
elif field_type == 'string':
|
|
|
|
|
|
return value
|
|
|
|
|
|
elif field_type.startswith('List<'):
|
|
|
|
|
|
if value == '':
|
|
|
|
|
|
return []
|
|
|
|
|
|
inner_type = field_type[5:-1]
|
|
|
|
|
|
items = value.split('#')
|
|
|
|
|
|
result = []
|
|
|
|
|
|
for item in items:
|
|
|
|
|
|
if item == '':
|
|
|
|
|
|
continue
|
|
|
|
|
|
result.append(self.get_type_value(inner_type, item))
|
|
|
|
|
|
return result
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 未知类型,返回原始值
|
|
|
|
|
|
return value
|
|
|
|
|
|
# endregion
|
|
|
|
|
|
|
|
|
|
|
|
def upload_cdn(self):
|
|
|
|
|
|
if self.cdn_config is None:
|
|
|
|
|
|
config.notification.append_msg('cdn配置为空,不上传cdn')
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self.cdn_config.enable is False:
|
|
|
|
|
|
config.notification.append_msg('cdn配置未开启, 不上传cdn')
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
filename = self.get_config_filename()
|
|
|
|
|
|
local_file_path = self.get_local_config_file_path()
|
|
|
|
|
|
if not os.path.exists(local_file_path):
|
|
|
|
|
|
return
|
|
|
|
|
|
storage_level_db_path = f"{self.cdn_config.dir}/{filename}"
|
|
|
|
|
|
generation = config.get_firebase_instance(self.project).upload_file(local_file_path, storage_level_db_path)
|
|
|
|
|
|
config.notification.append_msg(f"{filename}配置上传到cdn路径:{storage_level_db_path}成功")
|
|
|
|
|
|
cdn_url = f'{config.get_project_cdn(self.project)}/{storage_level_db_path}?generation={generation}'
|
|
|
|
|
|
config.notification.append_msg(f"配置最新下载链接url:{cdn_url}")
|
|
|
|
|
|
return generation
|
|
|
|
|
|
|
|
|
|
|
|
def update_remote_config(self, generation=None):
|
|
|
|
|
|
if self.remote_config is None:
|
|
|
|
|
|
config.notification.append_msg('remote配置为空,不更新RemoteConfig')
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if self.remote_config.enable is False:
|
|
|
|
|
|
config.notification.append_msg('remote配置未开启,不更新RemoteConfig')
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
filename = self.get_config_filename()
|
|
|
|
|
|
storage_level_db_path = f"{self.cdn_config.dir}/{filename}"
|
|
|
|
|
|
cdn = config.get_project_cdn(self.project)
|
|
|
|
|
|
if generation is None:
|
|
|
|
|
|
generation = self.firebase_helper.get_file_generation(storage_level_db_path)
|
|
|
|
|
|
level_db_cdn_url = f'{cdn}/{storage_level_db_path}' if generation is None else f'{cdn}/{storage_level_db_path}?generation={generation}'
|
2025-03-24 07:42:46 +00:00
|
|
|
|
key = self.get_remote_config_key()
|
2025-03-24 06:34:03 +00:00
|
|
|
|
group = None if self.remote_config.group == '' else self.remote_config.group
|
|
|
|
|
|
condition = None if self.remote_config.condition == '' else self.remote_config.condition
|
2025-03-24 07:47:54 +00:00
|
|
|
|
config.get_firebase_instance(self.project).update_remote_config_json_field_value(group, condition, key, self.env, level_db_cdn_url, True)
|
2025-03-24 07:42:46 +00:00
|
|
|
|
config.notification.append_msg(f"[group:{group}, condition:{condition}, key:{key} env:{self.env}] 云控更新成功")
|
2025-03-24 06:34:03 +00:00
|
|
|
|
|
|
|
|
|
|
def get_local_config_file_path(self):
|
|
|
|
|
|
local_file_path = f'temp_config/{self.get_config_filename()}'
|
|
|
|
|
|
return local_file_path
|
|
|
|
|
|
|
|
|
|
|
|
def get_config_filename(self):
|
|
|
|
|
|
cdn_filename = self.cdn_config.filename
|
|
|
|
|
|
if '#platform#' in cdn_filename:
|
2025-03-24 07:54:50 +00:00
|
|
|
|
if self.platform == str(config.platform.No.value).lower():
|
2025-03-24 06:34:03 +00:00
|
|
|
|
cdn_filename = cdn_filename.replace('#platform#', '')
|
|
|
|
|
|
else:
|
|
|
|
|
|
cdn_filename = cdn_filename.replace('#platform#', self.platform)
|
2025-03-24 08:00:05 +00:00
|
|
|
|
cdn_filename += f'-{self.cdn_config.version}-{self.env}.json'
|
2025-03-24 06:34:03 +00:00
|
|
|
|
return cdn_filename
|
|
|
|
|
|
|
2025-03-24 07:42:46 +00:00
|
|
|
|
def get_remote_config_key(self):
|
|
|
|
|
|
remote_key = self.remote_config.key
|
|
|
|
|
|
if '#platform#' in remote_key:
|
2025-03-24 07:54:50 +00:00
|
|
|
|
if self.platform == str(config.platform.No.value).lower():
|
2025-03-24 07:42:46 +00:00
|
|
|
|
remote_key = remote_key.replace('#platform#', '')
|
|
|
|
|
|
else:
|
|
|
|
|
|
remote_key = remote_key.replace('#platform#', self.platform)
|
|
|
|
|
|
return remote_key
|
|
|
|
|
|
|
2025-03-24 06:34:03 +00:00
|
|
|
|
project_id = None
|
|
|
|
|
|
platform = None
|
|
|
|
|
|
env = None
|
|
|
|
|
|
google_sheet_file_name = None
|
|
|
|
|
|
sheet_table_name = None
|
|
|
|
|
|
param_enable_upload_cdn = None
|
|
|
|
|
|
param_enable_upload_remote_config = None
|
|
|
|
|
|
|
|
|
|
|
|
if len(sys.argv) > 1:
|
|
|
|
|
|
project_id = sys.argv[1]
|
|
|
|
|
|
if len(sys.argv) > 2:
|
|
|
|
|
|
platform = sys.argv[2]
|
|
|
|
|
|
if len(sys.argv) > 3:
|
|
|
|
|
|
env = sys.argv[3]
|
|
|
|
|
|
if len(sys.argv) > 4:
|
|
|
|
|
|
google_sheet_file_name = sys.argv[4]
|
|
|
|
|
|
if len(sys.argv) > 5:
|
|
|
|
|
|
sheet_table_name = sys.argv[5]
|
|
|
|
|
|
if len(sys.argv) > 6:
|
|
|
|
|
|
param_enable_upload_cdn = sys.argv[6]
|
|
|
|
|
|
if len(sys.argv) > 7:
|
|
|
|
|
|
param_enable_upload_remote_config = sys.argv[7]
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2025-03-24 06:36:19 +00:00
|
|
|
|
if project_id is None:
|
|
|
|
|
|
config.notification.append_msg(f'参数错误[project_id is None]')
|
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
if platform is None:
|
|
|
|
|
|
config.notification.append_msg(f'参数错误[platform is None]')
|
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
if env is None:
|
|
|
|
|
|
config.notification.append_msg(f'参数错误[env is None]')
|
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
if google_sheet_file_name is None or sheet_table_name is None:
|
|
|
|
|
|
config.notification.append_msg(f'参数错误[google_sheet_file_name or sheet_table_name is None]')
|
2025-03-24 06:34:03 +00:00
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
config_generator = ConfigGenerator(google_sheet_file_name, sheet_table_name, project_id, platform, env)
|
|
|
|
|
|
config_json = config_generator.gen_config_json()
|
|
|
|
|
|
generation = None
|
2025-03-24 07:32:13 +00:00
|
|
|
|
if str(param_enable_upload_cdn).lower() == 'true':
|
2025-03-24 06:34:03 +00:00
|
|
|
|
generation = config_generator.upload_cdn()
|
2025-03-24 07:32:13 +00:00
|
|
|
|
if str(param_enable_upload_remote_config).lower() == 'true':
|
2025-03-24 06:34:03 +00:00
|
|
|
|
config_generator.update_remote_config(generation)
|
2025-03-24 07:56:33 +00:00
|
|
|
|
config.wechat_alert()
|
2025-03-24 06:34:03 +00:00
|
|
|
|
|