|
@@ -22,7 +22,6 @@ from vs_plg import IVSPlg
|
|
|
from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
|
|
from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError
|
|
|
from vs_log_macros import VSC_INFO, VSC_ERROR, VSC_DEBUG, VSC_WARN
|
|
from vs_log_macros import VSC_INFO, VSC_ERROR, VSC_DEBUG, VSC_WARN
|
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
|
-from utils.browser_util import get_browser
|
|
|
|
|
|
|
|
|
|
class BlsPlugin(IVSPlg):
|
|
class BlsPlugin(IVSPlg):
|
|
|
"""
|
|
"""
|
|
@@ -40,8 +39,7 @@ class BlsPlugin(IVSPlg):
|
|
|
self.is_healthy = True
|
|
self.is_healthy = True
|
|
|
|
|
|
|
|
# OCR 服务地址默认值
|
|
# OCR 服务地址默认值
|
|
|
- self.ocr_service_url = "http://127.0.0.1:8085/predict/bls?model=pytorch"
|
|
|
|
|
- self.browser = get_browser()
|
|
|
|
|
|
|
+ self.local_service_url = ""
|
|
|
|
|
|
|
|
def get_group_id(self) -> str:
|
|
def get_group_id(self) -> str:
|
|
|
return self.group_id
|
|
return self.group_id
|
|
@@ -54,8 +52,8 @@ class BlsPlugin(IVSPlg):
|
|
|
self.free_config = {}
|
|
self.free_config = {}
|
|
|
|
|
|
|
|
# 从配置中读取 OCR 服务地址,如果没有则使用默认
|
|
# 从配置中读取 OCR 服务地址,如果没有则使用默认
|
|
|
- if self.free_config.get("ocr_service_url"):
|
|
|
|
|
- self.ocr_service_url = self.free_config["ocr_service_url"]
|
|
|
|
|
|
|
+ if self.free_config.get("local_service_url"):
|
|
|
|
|
+ self.local_service_url = self.free_config["local_service_url"]
|
|
|
|
|
|
|
|
def health_check(self) -> bool:
|
|
def health_check(self) -> bool:
|
|
|
return self.is_healthy
|
|
return self.is_healthy
|
|
@@ -86,15 +84,18 @@ class BlsPlugin(IVSPlg):
|
|
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
|
form_data = self._extract_hidden_fields(soup)
|
|
form_data = self._extract_hidden_fields(soup)
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ real_user = None
|
|
|
|
|
+ real_pass = None
|
|
|
|
|
+
|
|
|
# 解析动态 ID (UserId1, Password1 等)
|
|
# 解析动态 ID (UserId1, Password1 等)
|
|
|
for inp in soup.find_all('input'):
|
|
for inp in soup.find_all('input'):
|
|
|
- iid = inp.get('id', '')
|
|
|
|
|
- if 'UserId' in iid and re.search(r'\d+', iid):
|
|
|
|
|
- form_data["UserIdKey"] = iid # 暂存 Key
|
|
|
|
|
- form_data["UserId"] = re.search(r'\d+', iid).group(0)
|
|
|
|
|
- if 'Password' in iid and re.search(r'\d+', iid):
|
|
|
|
|
- form_data["PasswordKey"] = iid # 暂存 Key
|
|
|
|
|
- form_data["Password"] = re.search(r'\d+', iid).group(0)
|
|
|
|
|
|
|
+ name = inp.get('name', '')
|
|
|
|
|
+ if inp.has_attr('required'):
|
|
|
|
|
+ if 'UserId' in name:
|
|
|
|
|
+ real_user = name
|
|
|
|
|
+ elif 'Password' in name:
|
|
|
|
|
+ real_pass = name
|
|
|
|
|
|
|
|
# 解析 data 参数 (用于验证码)
|
|
# 解析 data 参数 (用于验证码)
|
|
|
data_val = self._extract_js_var(resp.text, "iframeOpenUrl", r"data=([^']+)")
|
|
data_val = self._extract_js_var(resp.text, "iframeOpenUrl", r"data=([^']+)")
|
|
@@ -108,11 +109,8 @@ class BlsPlugin(IVSPlg):
|
|
|
payload["X-Requested-With"] = "XMLHttpRequest"
|
|
payload["X-Requested-With"] = "XMLHttpRequest"
|
|
|
payload["CaptchaData"] = captcha_token
|
|
payload["CaptchaData"] = captcha_token
|
|
|
# 填入账号密码
|
|
# 填入账号密码
|
|
|
- if "UserIdKey" in form_data:
|
|
|
|
|
- payload[form_data["UserIdKey"]] = self.config.account.username
|
|
|
|
|
- if "PasswordKey" in form_data:
|
|
|
|
|
- payload[form_data["PasswordKey"]] = self.config.account.password
|
|
|
|
|
-
|
|
|
|
|
|
|
+ payload[real_user] = self.config.account.username
|
|
|
|
|
+ payload[real_pass] = self.config.account.password
|
|
|
login_resp = self._perform_request('POST', submit_url, data=payload, headers=headers)
|
|
login_resp = self._perform_request('POST', submit_url, data=payload, headers=headers)
|
|
|
if login_resp.json()['success']:
|
|
if login_resp.json()['success']:
|
|
|
return
|
|
return
|
|
@@ -176,14 +174,20 @@ class BlsPlugin(IVSPlg):
|
|
|
avail_json = json.loads(avail_str)
|
|
avail_json = json.loads(avail_str)
|
|
|
# 提取日期
|
|
# 提取日期
|
|
|
dates = [x['DateText'] for x in avail_json['ad'] if x['SingleSlotAvailable']]
|
|
dates = [x['DateText'] for x in avail_json['ad'] if x['SingleSlotAvailable']]
|
|
|
-
|
|
|
|
|
|
|
+ res.city = self.free_config.get('city', '')
|
|
|
|
|
+ res.country = self.free_config.get('country', '')
|
|
|
|
|
+ res.visa_type = self.free_config.get('visa_type', '')
|
|
|
|
|
+ res.routing_key = self.free_config.get('routing_key', '')
|
|
|
if dates:
|
|
if dates:
|
|
|
res.success = True
|
|
res.success = True
|
|
|
res.availability_status = AvailabilityStatus.Available
|
|
res.availability_status = AvailabilityStatus.Available
|
|
|
res.earliest_date = dates[0]
|
|
res.earliest_date = dates[0]
|
|
|
for d in dates:
|
|
for d in dates:
|
|
|
- da = VSQueryResult.DateAvailability(date=d)
|
|
|
|
|
- da.times.append(VSQueryResult.DateAvailability.TimeSlot(time="00:00", label="Available"))
|
|
|
|
|
|
|
+ da = VSQueryResult.DateAvailability()
|
|
|
|
|
+ da.date = d
|
|
|
|
|
+ da.times = []
|
|
|
|
|
+ time_slot = VSQueryResult.DateAvailability.TimeSlot(time="00:00", label="Available")
|
|
|
|
|
+ da.times.append(time_slot)
|
|
|
res.availability.append(da)
|
|
res.availability.append(da)
|
|
|
else:
|
|
else:
|
|
|
res.success = False
|
|
res.success = False
|
|
@@ -239,7 +243,11 @@ class BlsPlugin(IVSPlg):
|
|
|
otp_code = self._read_otp_email(wait_sec=30)
|
|
otp_code = self._read_otp_email(wait_sec=30)
|
|
|
|
|
|
|
|
# 验证 OTP
|
|
# 验证 OTP
|
|
|
- verify_payload = {"Code": otp_code, "Value": ma_form.get('EmailCode'), "Id": ma_form.get('Id')}
|
|
|
|
|
|
|
+ verify_payload = {
|
|
|
|
|
+ "Code": otp_code,
|
|
|
|
|
+ "Value": ma_form.get('EmailCode'),
|
|
|
|
|
+ "Id": ma_form.get('Id')
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
headers['requestverificationtoken'] = req_token
|
|
headers['requestverificationtoken'] = req_token
|
|
|
v_resp = self._perform_request('POST', f"https://{domain}/Global/blsappointment/VerifyEmail", data=verify_payload, headers=headers)
|
|
v_resp = self._perform_request('POST', f"https://{domain}/Global/blsappointment/VerifyEmail", data=verify_payload, headers=headers)
|
|
@@ -337,7 +345,7 @@ class BlsPlugin(IVSPlg):
|
|
|
1. 发送 OPTIONS 请求
|
|
1. 发送 OPTIONS 请求
|
|
|
2. 发送实际请求
|
|
2. 发送实际请求
|
|
|
"""
|
|
"""
|
|
|
- print(f'[perform request] {method} {url}')
|
|
|
|
|
|
|
+ print(f'[perform request] {method} {url} {data} {json_data} {params}')
|
|
|
resp = self.session.request(method, url, headers=headers, data=data, json=json_data, params=params, timeout=30)
|
|
resp = self.session.request(method, url, headers=headers, data=data, json=json_data, params=params, timeout=30)
|
|
|
VSC_INFO('bls_plg', resp.text)
|
|
VSC_INFO('bls_plg', resp.text)
|
|
|
if resp.status_code == 200:
|
|
if resp.status_code == 200:
|
|
@@ -365,68 +373,54 @@ class BlsPlugin(IVSPlg):
|
|
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
|
|
|
}
|
|
}
|
|
|
resp = self._perform_request("GET", url, headers=headers)
|
|
resp = self._perform_request("GET", url, headers=headers)
|
|
|
|
|
+ soup = BeautifulSoup(resp.text, 'html.parser')
|
|
|
|
|
+ resp = requests.post(
|
|
|
|
|
+ f'{self.local_service_url}/browser/visable_captchas',
|
|
|
|
|
+ data=resp.text,
|
|
|
|
|
+ headers={"Content-Type": "text/plain"},
|
|
|
|
|
+ timeout=10
|
|
|
|
|
+ )
|
|
|
|
|
+ result = resp.json()
|
|
|
|
|
+ if result.get('status') != 'success':
|
|
|
|
|
+ raise BizLogicError(message='Broswer task failed')
|
|
|
|
|
|
|
|
- with open("tmp.html", 'w') as f:
|
|
|
|
|
- f.write(resp.text)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ numbers = result['data']['number']
|
|
|
|
|
+ image_ids = result['data']['image_ids']
|
|
|
selected_ids = []
|
|
selected_ids = []
|
|
|
- html_file_path = Path("tmp.html").resolve()
|
|
|
|
|
- file_url = f'file://{html_file_path}'
|
|
|
|
|
- self.browser.get(file_url)
|
|
|
|
|
- captions_ele = self.browser.ele('xpath://*[@id="captcha-main-div"]/div/div[1]', timeout=5)
|
|
|
|
|
- if not captions_ele:
|
|
|
|
|
- raise NotFoundError(message='Captions elements not found')
|
|
|
|
|
- caption_eles = captions_ele.children()
|
|
|
|
|
- caption_text = ''
|
|
|
|
|
- for caption in caption_eles:
|
|
|
|
|
- if not caption.states.is_covered:
|
|
|
|
|
- caption_text = caption.text
|
|
|
|
|
-
|
|
|
|
|
- numbers = re.findall(r'\d+', caption_text)[0]
|
|
|
|
|
- captcha_images_ele = self.browser.ele('xpath://*[@id="captcha-main-div"]/div/div[2]')
|
|
|
|
|
- captcha_image_eles = captcha_images_ele.children()
|
|
|
|
|
- rect_dict = {}
|
|
|
|
|
- for captcha_image in captcha_image_eles:
|
|
|
|
|
- img = captcha_image.ele('.captcha-img')
|
|
|
|
|
- if img.states.has_rect:
|
|
|
|
|
- rect_dict[img._backend_id] = img.states.has_rect
|
|
|
|
|
- for captcha_image in captcha_image_eles:
|
|
|
|
|
- img = captcha_image.ele('.captcha-img')
|
|
|
|
|
- if img.states.has_rect and img.states.is_covered == False:
|
|
|
|
|
- img_src = img.attr('src')
|
|
|
|
|
- if img_src and img_src.startswith('data:image'):
|
|
|
|
|
- base64_data = re.sub('^data:image/.+;base64,', '', img_src)
|
|
|
|
|
- img_bytes = base64.b64decode(base64_data)
|
|
|
|
|
-
|
|
|
|
|
- ocr_resp = requests.post(
|
|
|
|
|
- self.ocr_service_url,
|
|
|
|
|
- data=img_bytes,
|
|
|
|
|
- headers={"Content-Type": "application/octet-stream"},
|
|
|
|
|
- timeout=5
|
|
|
|
|
- )
|
|
|
|
|
- if ocr_resp.status_code == 200:
|
|
|
|
|
- res_json = ocr_resp.json()
|
|
|
|
|
- ocr_res = res_json.get('data', '').replace('$', '')[:3]
|
|
|
|
|
-
|
|
|
|
|
- VSC_INFO("bls_plg", f'ocr captcha id={captcha_image.attr("id")} result={ocr_res}, target={numbers}')
|
|
|
|
|
-
|
|
|
|
|
- if ocr_res == numbers:
|
|
|
|
|
- eid = captcha_image.attr('id')
|
|
|
|
|
- selected_ids.append(eid)
|
|
|
|
|
- else:
|
|
|
|
|
- raise BizLogicError(message='Captcha server response error')
|
|
|
|
|
-
|
|
|
|
|
|
|
+ for sid in image_ids:
|
|
|
|
|
+ div = soup.find("div", id=sid)
|
|
|
|
|
+ img = div.find("img")
|
|
|
|
|
+ src = img.get("src")
|
|
|
|
|
+ base64_data = src.split("base64,", 1)[1]
|
|
|
|
|
+ img_bytes = base64.b64decode(base64_data)
|
|
|
|
|
+ ocr_resp = requests.post(
|
|
|
|
|
+ f'{self.local_service_url}/predict/bls?model=pytorch',
|
|
|
|
|
+ data=img_bytes,
|
|
|
|
|
+ headers={"Content-Type": "application/octet-stream"},
|
|
|
|
|
+ timeout=5
|
|
|
|
|
+ )
|
|
|
|
|
+ if ocr_resp.status_code == 200:
|
|
|
|
|
+ res_json = ocr_resp.json()
|
|
|
|
|
+ ocr_res = res_json.get('data', '').replace('$', '')[:3]
|
|
|
|
|
+ VSC_INFO("bls_plg", f'ocr captcha id={sid} result={ocr_res}, target={numbers}')
|
|
|
|
|
+ if ocr_res == numbers:
|
|
|
|
|
+ selected_ids.append(sid)
|
|
|
|
|
+ else:
|
|
|
|
|
+ raise BizLogicError(message='Captcha server response error')
|
|
|
if not selected_ids:
|
|
if not selected_ids:
|
|
|
raise BizLogicError(message='Captcha selected ids is empty')
|
|
raise BizLogicError(message='Captcha selected ids is empty')
|
|
|
- VSC_INFO("bls_plg", f'select_ids={selected_ids}')
|
|
|
|
|
- soup = BeautifulSoup(resp.text, 'html.parser')
|
|
|
|
|
|
|
+
|
|
|
# 3. 提交选中结果
|
|
# 3. 提交选中结果
|
|
|
|
|
+ VSC_INFO("bls_plg", f'select_ids={selected_ids}')
|
|
|
form = self._extract_hidden_fields(soup)
|
|
form = self._extract_hidden_fields(soup)
|
|
|
form['SelectedImages'] = ",".join(selected_ids)
|
|
form['SelectedImages'] = ",".join(selected_ids)
|
|
|
submit_url = f"https://{domain}/Global/{'CaptchaPublic' if data else 'NewCaptcha'}/SubmitCaptcha"
|
|
submit_url = f"https://{domain}/Global/{'CaptchaPublic' if data else 'NewCaptcha'}/SubmitCaptcha"
|
|
|
headers["X-Requested-With"] = "XMLHttpRequest"
|
|
headers["X-Requested-With"] = "XMLHttpRequest"
|
|
|
resp = self._perform_request('POST', submit_url, headers=headers, data=form)
|
|
resp = self._perform_request('POST', submit_url, headers=headers, data=form)
|
|
|
- return resp.json()['captcha']
|
|
|
|
|
|
|
+ if data:
|
|
|
|
|
+ return resp.json()['captcha']
|
|
|
|
|
+ else:
|
|
|
|
|
+ return resp.json()['cd']
|
|
|
|
|
|
|
|
def _extract_hidden_fields(self, soup) -> Dict:
|
|
def _extract_hidden_fields(self, soup) -> Dict:
|
|
|
params = {}
|
|
params = {}
|
|
@@ -450,10 +444,10 @@ class BlsPlugin(IVSPlg):
|
|
|
"""
|
|
"""
|
|
|
构造 VisaType 提交参数 (对应原代码 parse_visatype_form)
|
|
构造 VisaType 提交参数 (对应原代码 parse_visatype_form)
|
|
|
"""
|
|
"""
|
|
|
- # 1. 基础表单参数 (__RequestVerificationToken 等)
|
|
|
|
|
|
|
+ # 基础表单参数 (__RequestVerificationToken 等)
|
|
|
params = self._extract_hidden_fields(soup)
|
|
params = self._extract_hidden_fields(soup)
|
|
|
|
|
|
|
|
- # 2. 提取页面中的 JS 数据变量
|
|
|
|
|
|
|
+ # 提取页面中的 JS 数据变量
|
|
|
def get_js_data(var_name):
|
|
def get_js_data(var_name):
|
|
|
try:
|
|
try:
|
|
|
# 匹配 var name = [...]; 结构
|
|
# 匹配 var name = [...]; 结构
|
|
@@ -464,86 +458,105 @@ class BlsPlugin(IVSPlg):
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
VSC_DEBUG("bls_plg", f"Failed to parse JS var {var_name}: {e}")
|
|
VSC_DEBUG("bls_plg", f"Failed to parse JS var {var_name}: {e}")
|
|
|
return []
|
|
return []
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 读取配置
|
|
|
|
|
+ query_selector = self.free_config.get("query_selector", {})
|
|
|
|
|
+ cfg_jur = query_selector.get("jurisdiction")
|
|
|
|
|
+ cfg_loc = query_selector.get("location")
|
|
|
|
|
+ cfg_type = query_selector.get("visa_type")
|
|
|
|
|
+ cfg_subtype = query_selector.get("visa_subtype")
|
|
|
|
|
+ cfg_cat = query_selector.get("appointment_category")
|
|
|
|
|
+
|
|
|
|
|
+ jur_value = None
|
|
|
|
|
+ loc_value = None
|
|
|
|
|
+ type_value = None
|
|
|
|
|
+ subtype_value = None
|
|
|
|
|
+ cat_value = None
|
|
|
|
|
+
|
|
|
|
|
+ resp = requests.post(
|
|
|
|
|
+ f'{self.local_service_url}/browser/visatype_visable',
|
|
|
|
|
+ data=html,
|
|
|
|
|
+ headers={"Content-Type": "text/plain"},
|
|
|
|
|
+ timeout=10
|
|
|
|
|
+ )
|
|
|
|
|
+ result = resp.json()
|
|
|
|
|
+ if result.get('status') != 'success':
|
|
|
|
|
+ raise BizLogicError(message='Broswer task failed')
|
|
|
|
|
+
|
|
|
|
|
+ jur_id = result['data']['jur_id']
|
|
|
|
|
+ loc_id = result['data']['loc_id']
|
|
|
|
|
+ type_id = result['data']['type_id']
|
|
|
|
|
+ subtype_id = result['data']['subtype_id']
|
|
|
|
|
+ cat_id = result['data']['cat_id']
|
|
|
|
|
+
|
|
|
jurisdiction_list = get_js_data("jurisdictionData")
|
|
jurisdiction_list = get_js_data("jurisdictionData")
|
|
|
location_list = get_js_data("locationData")
|
|
location_list = get_js_data("locationData")
|
|
|
visa_type_list = get_js_data("visaIdData")
|
|
visa_type_list = get_js_data("visaIdData")
|
|
|
visa_subtype_list = get_js_data("visasubIdData")
|
|
visa_subtype_list = get_js_data("visasubIdData")
|
|
|
app_category_list = get_js_data("AppointmentCategoryIdData")
|
|
app_category_list = get_js_data("AppointmentCategoryIdData")
|
|
|
|
|
|
|
|
- # 3. 读取配置
|
|
|
|
|
- cfg_jur = self.free_config.get("jurisdiction")
|
|
|
|
|
- cfg_loc = self.free_config.get("location")
|
|
|
|
|
- cfg_type = self.free_config.get("visaType")
|
|
|
|
|
- cfg_subtype = self.free_config.get("visaSubType")
|
|
|
|
|
- cfg_cat = self.free_config.get("appointmentCategory", "Normal")
|
|
|
|
|
-
|
|
|
|
|
- # 4. 匹配 ID
|
|
|
|
|
- jur_id = None
|
|
|
|
|
- loc_id = None
|
|
|
|
|
- type_id = None
|
|
|
|
|
- subtype_id = None
|
|
|
|
|
- cat_id = None
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # 4. 匹配 Value
|
|
|
# (A) Appointment Category
|
|
# (A) Appointment Category
|
|
|
for item in app_category_list:
|
|
for item in app_category_list:
|
|
|
if item.get("Name") == cfg_cat:
|
|
if item.get("Name") == cfg_cat:
|
|
|
- cat_id = item.get("Id")
|
|
|
|
|
|
|
+ cat_value = item.get("Id")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# (B) Jurisdiction (如果配置了)
|
|
# (B) Jurisdiction (如果配置了)
|
|
|
if cfg_jur and jurisdiction_list:
|
|
if cfg_jur and jurisdiction_list:
|
|
|
for item in jurisdiction_list:
|
|
for item in jurisdiction_list:
|
|
|
if item.get("Name") == cfg_jur:
|
|
if item.get("Name") == cfg_jur:
|
|
|
- jur_id = item.get("Id")
|
|
|
|
|
|
|
+ jur_value = item.get("Id")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# (C) Location
|
|
# (C) Location
|
|
|
for item in location_list:
|
|
for item in location_list:
|
|
|
if item.get("Name") == cfg_loc:
|
|
if item.get("Name") == cfg_loc:
|
|
|
- loc_id = item.get("Id")
|
|
|
|
|
|
|
+ loc_value = item.get("Id")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# (D) Visa Type (需匹配 LocationId)
|
|
# (D) Visa Type (需匹配 LocationId)
|
|
|
- if loc_id:
|
|
|
|
|
|
|
+ if loc_value:
|
|
|
for item in visa_type_list:
|
|
for item in visa_type_list:
|
|
|
# 比较 Name 和 LocationId
|
|
# 比较 Name 和 LocationId
|
|
|
- if item.get("Name") == cfg_type and str(item.get("LocationId")) == str(loc_id):
|
|
|
|
|
- type_id = item.get("Id")
|
|
|
|
|
|
|
+ if item.get("Name") == cfg_type and str(item.get("LocationId")) == str(loc_value):
|
|
|
|
|
+ type_value = item.get("Id")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# (E) Visa SubType (需匹配 VisaType Value)
|
|
# (E) Visa SubType (需匹配 VisaType Value)
|
|
|
- if type_id:
|
|
|
|
|
|
|
+ if type_value:
|
|
|
for item in visa_subtype_list:
|
|
for item in visa_subtype_list:
|
|
|
# BLS 逻辑: visasubIdData 中的 Value 字段对应 VisaTypeId
|
|
# BLS 逻辑: visasubIdData 中的 Value 字段对应 VisaTypeId
|
|
|
- if item.get("Name") == cfg_subtype and str(item.get("Value")) == str(type_id):
|
|
|
|
|
- subtype_id = item.get("Id")
|
|
|
|
|
|
|
+ if item.get("Name") == cfg_subtype and str(item.get("Value")) == str(type_value):
|
|
|
|
|
+ subtype_value = item.get("Id")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
# 5. 构造动态参数 & 校验
|
|
# 5. 构造动态参数 & 校验
|
|
|
- if not cat_id:
|
|
|
|
|
|
|
+ if not cat_value:
|
|
|
raise NotFoundError(message=f"Config: AppCategory '{cfg_cat}' not found")
|
|
raise NotFoundError(message=f"Config: AppCategory '{cfg_cat}' not found")
|
|
|
- params[f"AppointmentCategoryId{cat_id}"] = cat_id
|
|
|
|
|
|
|
+ params[f"AppointmentCategoryId{cat_id}"] = cat_value
|
|
|
|
|
|
|
|
if cfg_jur:
|
|
if cfg_jur:
|
|
|
- if not jur_id:
|
|
|
|
|
|
|
+ if not jur_value:
|
|
|
raise NotFoundError(message=f"Config: Jurisdiction '{cfg_jur}' not found")
|
|
raise NotFoundError(message=f"Config: Jurisdiction '{cfg_jur}' not found")
|
|
|
- params[f"JurisdictionId{jur_id}"] = jur_id
|
|
|
|
|
|
|
+ params[f"JurisdictionId{jur_id}"] = jur_value
|
|
|
|
|
|
|
|
- if not loc_id:
|
|
|
|
|
|
|
+ if not loc_value:
|
|
|
raise NotFoundError(message=f"Config: Location '{cfg_loc}' not found")
|
|
raise NotFoundError(message=f"Config: Location '{cfg_loc}' not found")
|
|
|
- params[f"Location{loc_id}"] = loc_id
|
|
|
|
|
|
|
+ params[f"Location{loc_id}"] = loc_value
|
|
|
|
|
|
|
|
- if not type_id:
|
|
|
|
|
|
|
+ if not type_value:
|
|
|
raise NotFoundError(message=f"Config: VisaType '{cfg_type}' not found for Loc '{cfg_loc}'")
|
|
raise NotFoundError(message=f"Config: VisaType '{cfg_type}' not found for Loc '{cfg_loc}'")
|
|
|
- params[f"VisaType{type_id}"] = type_id
|
|
|
|
|
|
|
+ params[f"VisaType{type_id}"] = type_value
|
|
|
|
|
|
|
|
- if not subtype_id:
|
|
|
|
|
|
|
+ if not subtype_value:
|
|
|
raise NotFoundError(message=f"Config: VisaSubType '{cfg_subtype}' not found")
|
|
raise NotFoundError(message=f"Config: VisaSubType '{cfg_subtype}' not found")
|
|
|
- params[f"VisaSubType{subtype_id}"] = subtype_id
|
|
|
|
|
|
|
+ params[f"VisaSubType{subtype_id}"] = subtype_value
|
|
|
|
|
|
|
|
# 固定参数
|
|
# 固定参数
|
|
|
- params["AppointmentFor1"] = "Individual"
|
|
|
|
|
|
|
+ for k in list(params.keys()):
|
|
|
|
|
+ if k.startswith("AppointmentFor"):
|
|
|
|
|
+ params[k] = "Individual"
|
|
|
|
|
|
|
|
# 6. 构造 ResponseData (行为轨迹模拟)
|
|
# 6. 构造 ResponseData (行为轨迹模拟)
|
|
|
# BLS 后端会校验这个字段,模拟用户选择下拉框的时间间隔
|
|
# BLS 后端会校验这个字段,模拟用户选择下拉框的时间间隔
|