diff --git a/reallife_asset/init.py b/reallife_asset/init.py index 2eed0cd..84ee849 100644 --- a/reallife_asset/init.py +++ b/reallife_asset/init.py @@ -643,6 +643,126 @@ async def rl_sync_asset_status_user(org_id, asset_id, downapp_id): return {"success": True, "status": status, "url": url} +async def rl_handle_callback(byted_token, project_name="default"): + """ + Callback handler: Volcengine POSTs here after H5 auth completes. + Looks up local group by byted_token, queries vendor for result, + then registers rl_org_group mapping. + """ + dbname = _get_dbname() + db = DBPools() + + # 1. Find local group by byted_token + async with db.sqlorContext(dbname) as sor: + recs = await sor.R("rl_asset_group", {"byted_token": byted_token}) + if not recs: + return {"success": False, "message": "未找到对应的认证会话"} + rec = recs[0] + local_group_id = rec.id + org_id = rec.org_id + + # Already processed? + if rec.status == "active" and rec.vendor_group_id: + debug(f"callback already processed for group {local_group_id}") + return { + "success": True, + "local_group_id": local_group_id, + "vendor_group_id": rec.vendor_group_id, + "message": "已处理", + } + + # 2. Get vendor keys + async with db.sqlorContext(dbname) as sor: + vrecs = await sor.R("rl_vendor_config", {"vendor": "volcengine"}) + if not vrecs: + return {"success": False, "message": "供应商配置不存在"} + vrec = vrecs[0] + if vrec.status != "active": + return {"success": False, "message": "供应商服务已停用"} + env = ServerEnv() + ak = env.password_decode(vrec.ak) + sk = env.password_decode(vrec.sk) + + # 3. Query vendor for result + client = _get_client("volcengine", ak, sk) + result = client.get_visual_validate_result(byted_token, project_name) + + if "error" in result: + error(f"callback query vendor error: {result}") + return {"success": False, "message": result.get("error", "查询失败")} + + vendor_group_id = result.get("GroupId", "") + if not vendor_group_id: + return {"success": False, "message": "尚未完成认证或认证失败"} + + # 4. Register rl_org_group mapping + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + mapping_id = getID() + async with db.sqlorContext(dbname) as sor: + existing = await sor.R("rl_org_group", { + "org_id": org_id, + "vendor": "volcengine", + }) + if existing: + await sor.U("rl_org_group", { + "vendor_group_id": vendor_group_id, + "local_group_id": local_group_id, + "update_time": now, + }, {"id": existing[0].id}) + else: + await sor.I("rl_org_group", { + "id": mapping_id, + "org_id": org_id, + "vendor": "volcengine", + "vendor_group_id": vendor_group_id, + "local_group_id": local_group_id, + "status": "active", + "create_time": now, + }) + + # 5. Update rl_asset_group status + async with db.sqlorContext(dbname) as sor: + await sor.U("rl_asset_group", { + "vendor_group_id": vendor_group_id, + "status": "active", + "update_time": now, + }, {"id": local_group_id}) + + debug(f"callback processed: org={org_id}, group={local_group_id}, " + f"vendor_group={vendor_group_id}") + return { + "success": True, + "local_group_id": local_group_id, + "vendor_group_id": vendor_group_id, + } + + +async def rl_query_groups(org_id): + """ + Client API: Query authenticated group_ids for an org. + Returns list of (local_group_id, vendor_group_id, status) mappings. + """ + dbname = _get_dbname() + db = DBPools() + + async with db.sqlorContext(dbname) as sor: + recs = await sor.R("rl_org_group", {"org_id": org_id}) + if not recs: + return {"success": True, "groups": []} + + groups = [] + for r in recs: + groups.append({ + "local_group_id": r.local_group_id, + "vendor_group_id": r.vendor_group_id, + "vendor": r.vendor, + "status": r.status, + "create_time": getattr(r, "create_time", ""), + }) + + return {"success": True, "groups": groups} + + # ============================================================ # Module loader # ============================================================ @@ -664,5 +784,7 @@ def load_reallife_asset(): g.rl_check_validate_and_map = rl_check_validate_and_map g.rl_upload_user = rl_upload_user g.rl_sync_asset_status_user = rl_sync_asset_status_user + g.rl_handle_callback = rl_handle_callback + g.rl_query_groups = rl_query_groups return True diff --git a/scripts/load_path.py b/scripts/load_path.py index 892d669..25c706d 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -21,7 +21,9 @@ python = os.path.join(sage_root, "py3/bin/python") set_perm = os.path.join(sage_root, "set_role_perm.py") # Permission definitions -paths_any = [] # No login required +paths_any = [ + "/reallife_asset/api/rl_callback.dspy", # Volcengine callback - no auth +] paths_logined = [ "/reallife_asset", "/reallife_asset/index.ui", @@ -50,12 +52,7 @@ paths_logined = [ "/reallife_asset/api/rl_verify.dspy", "/reallife_asset/api/rl_upload.dspy", "/reallife_asset/api/rl_status.dspy", - # Ops management CRUD - "/reallife_asset/api/rl_app_user_create.dspy", - "/reallife_asset/api/rl_app_user_update.dspy", - "/reallife_asset/api/rl_app_user_delete.dspy", - "/reallife_asset/rl_app_user_list", - "/reallife_asset/rl_app_user_list/index.ui", + "/reallife_asset/api/rl_query_groups.dspy", # Vendor Config CRUD "/reallife_asset/api/rl_vendor_config_create.dspy", "/reallife_asset/api/rl_vendor_config_update.dspy", diff --git a/wwwroot/api/rl_app_user_create.dspy b/wwwroot/api/rl_app_user_create.dspy deleted file mode 100644 index cefd4bc..0000000 --- a/wwwroot/api/rl_app_user_create.dspy +++ /dev/null @@ -1,32 +0,0 @@ -# Create app record -org_id = params_kw.get('org_id', (await get_userorgid()) or '0') -id = params_kw.get('id', getID()) -now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - -# Decrypt keys if provided by form? No, usually Ops enters plain text, we encrypt. -ak = params_kw.get('ak', '') -sk = params_kw.get('sk', '') -env = ServerEnv() -if ak: ak = env.password_encode(ak) -if sk: sk = env.password_encode(sk) - -data = { - "id": id, - "org_id": org_id, - "downapp_id": params_kw.get('downapp_id', ''), - "vendor": params_kw.get('vendor', 'volcengine'), - "status": params_kw.get('status', 'pending'), - "ak": ak, - "sk": sk, - "callback_url": params_kw.get('callback_url', ''), - "remark": params_kw.get('remark', ''), - "create_time": now, - "update_time": now -} - -db = DBPools() -dbname = get_module_dbname('reallife_asset') -async with db.sqlorContext(dbname) as sor: - await sor.I("rl_app_user", data) - -return {"success": True, "id": id} diff --git a/wwwroot/api/rl_app_user_delete.dspy b/wwwroot/api/rl_app_user_delete.dspy deleted file mode 100644 index bcdf815..0000000 --- a/wwwroot/api/rl_app_user_delete.dspy +++ /dev/null @@ -1,9 +0,0 @@ -id = params_kw.get('id', '') -if not id: return {"success": False, "message": "id required"} - -db = DBPools() -dbname = get_module_dbname('reallife_asset') -async with db.sqlorContext(dbname) as sor: - await sor.D("rl_app_user", {"id": id}) - -return {"success": True} diff --git a/wwwroot/api/rl_app_user_update.dspy b/wwwroot/api/rl_app_user_update.dspy deleted file mode 100644 index ab12bd9..0000000 --- a/wwwroot/api/rl_app_user_update.dspy +++ /dev/null @@ -1,31 +0,0 @@ -id = params_kw.get('id', '') -if not id: return {"success": False, "message": "id required"} - -db = DBPools() -dbname = get_module_dbname('reallife_asset') -async with db.sqlorContext(dbname) as sor: - recs = await sor.R("rl_app_user", {"id": id}) - if not recs: return {"success": False, "message": "Not found"} - -# Prepare update data -upd = {} -for k in ['status', 'remark', 'callback_url', 'vendor']: - if params_kw.get(k): - upd[k] = params_kw.get(k) - -# Handle keys encryption -ak = params_kw.get('ak', None) -sk = params_kw.get('sk', None) -if ak is not None: - env = ServerEnv() - upd['ak'] = env.password_encode(ak) -if sk is not None: - env = ServerEnv() - upd['sk'] = env.password_encode(sk) - -upd['update_time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - -async with db.sqlorContext(dbname) as sor: - await sor.U("rl_app_user", upd, {"id": id}) - -return {"success": True} diff --git a/wwwroot/api/rl_callback.dspy b/wwwroot/api/rl_callback.dspy new file mode 100644 index 0000000..2d2efd0 --- /dev/null +++ b/wwwroot/api/rl_callback.dspy @@ -0,0 +1,31 @@ +import json + +# Volcengine callback POSTs JSON body with BytedToken and result info. +# The callback URL is configured when calling CreateVisualValidateSession. +# Typical payload: {"BytedToken": "...", "ReqUUID": "...", "Status": "..."} + +# Try to parse JSON body first +body_str = http_request.get("body", "") or "" +byted_token = "" +project_name = "default" + +try: + body = json.loads(body_str) if body_str else {} + byted_token = body.get("BytedToken", "") + # Also check alternative field names + if not byted_token: + byted_token = body.get("byted_token", "") + if not byted_token: + byted_token = body.get("Token", "") +except: + byted_token = "" + +# Fallback: check query params +if not byted_token: + byted_token = params_kw.get("BytedToken", params_kw.get("byted_token", "")) + +if not byted_token: + return {"success": False, "message": "缺少 BytedToken 参数"} + +result = await rl_handle_callback(byted_token, project_name) +return result diff --git a/wwwroot/api/rl_query_groups.dspy b/wwwroot/api/rl_query_groups.dspy new file mode 100644 index 0000000..129ae71 --- /dev/null +++ b/wwwroot/api/rl_query_groups.dspy @@ -0,0 +1,4 @@ +org_id = (await get_userorgid()) or '0' + +result = await rl_query_groups(org_id) +return result