feat: add downapp user API interfaces and ops management table
- New table rl_app_user for application tracking - APIs: rl_apply, rl_verify, rl_upload, rl_status, rl_check_app_status - Ops CRUD for managing applications and keys - Multi-vendor support reserved via vendor field
This commit is contained in:
parent
78036b9640
commit
530f337704
43
json/rl_app_user_list.json
Normal file
43
json/rl_app_user_list.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"tblname": "rl_app_user",
|
||||
"alias": "rl_app_user_list",
|
||||
"title": "真人素材申请管理",
|
||||
"params": {
|
||||
"logined_userorgid": "org_id",
|
||||
"browserfields": {
|
||||
"id": {
|
||||
"title": "ID",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"downapp_id": {
|
||||
"title": "用户ID",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"vendor": {
|
||||
"title": "供应商",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"status": {
|
||||
"title": "状态",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"callback_url": {
|
||||
"title": "回调URL",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"create_time": {
|
||||
"title": "申请时间",
|
||||
"widgettype": "Text"
|
||||
},
|
||||
"update_time": {
|
||||
"title": "更新时间",
|
||||
"widgettype": "Text"
|
||||
}
|
||||
},
|
||||
"editable": {
|
||||
"new_data_url": "{{entire_url('../api/rl_app_user_create.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/rl_app_user_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/rl_app_user_delete.dspy')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
109
models/rl_app_user.json
Normal file
109
models/rl_app_user.json
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "rl_app_user",
|
||||
"title": "用户真人素材申请",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "主键",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
"title": "机构",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"default": "0"
|
||||
},
|
||||
{
|
||||
"name": "downapp_id",
|
||||
"title": "申请用户ID",
|
||||
"type": "str",
|
||||
"length": 32
|
||||
},
|
||||
{
|
||||
"name": "vendor",
|
||||
"title": "供应商",
|
||||
"type": "str",
|
||||
"length": 50
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"title": "状态",
|
||||
"type": "str",
|
||||
"length": 20,
|
||||
"default": "pending"
|
||||
},
|
||||
{
|
||||
"name": "ak",
|
||||
"title": "AccessKey(加密)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "sk",
|
||||
"title": "SecretKey(加密)",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "callback_url",
|
||||
"title": "回调URL",
|
||||
"type": "str",
|
||||
"length": 500
|
||||
},
|
||||
{
|
||||
"name": "remark",
|
||||
"title": "备注",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "create_time",
|
||||
"title": "创建时间",
|
||||
"type": "datetime"
|
||||
},
|
||||
{
|
||||
"name": "update_time",
|
||||
"title": "更新时间",
|
||||
"type": "datetime"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_rl_app_user_org",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"org_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_rl_app_user_user",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"downapp_id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "vendor",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='rl_vendor'"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='rl_app_status'"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -387,6 +387,122 @@ async def rl_sync_assets_from_vendor(org_id, local_group_id,
|
||||
return {"success": True, "synced": synced}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Downapp User API Proxies
|
||||
# ============================================================
|
||||
|
||||
async def _get_user_keys(downapp_id, vendor="volcengine"):
|
||||
"""Helper: Check application status and return decrypted keys."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_app_user", {"downapp_id": downapp_id, "vendor": vendor})
|
||||
if not recs:
|
||||
return {"success": False, "message": "未申请或供应商不支持"}
|
||||
rec = recs[0]
|
||||
if rec.status != "active":
|
||||
return {"success": False, "message": f"申请状态: {rec.status},未通过审批"}
|
||||
|
||||
env = ServerEnv()
|
||||
ak = env.password_decode(rec.ak)
|
||||
sk = env.password_decode(rec.sk)
|
||||
return {"success": True, "ak": ak, "sk": sk, "callback_url": rec.callback_url}
|
||||
|
||||
|
||||
async def rl_apply(org_id, downapp_id, vendor, callback_url):
|
||||
"""User applies for real person asset service."""
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
# Check if already exists
|
||||
existing = await sor.R("rl_app_user", {"downapp_id": downapp_id, "vendor": vendor})
|
||||
if existing:
|
||||
return {"success": False, "message": "已提交申请,请等待审批"}
|
||||
|
||||
aid = getID()
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
await sor.I("rl_app_user", {
|
||||
"id": aid,
|
||||
"org_id": org_id,
|
||||
"downapp_id": downapp_id,
|
||||
"vendor": vendor,
|
||||
"status": "pending",
|
||||
"callback_url": callback_url,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
return {"success": True, "app_id": aid, "status": "pending"}
|
||||
|
||||
|
||||
async def rl_verify_user(org_id, group_id, downapp_id, project_name="default"):
|
||||
"""User proxy: Check app -> Get keys -> Call vendor."""
|
||||
keys = await _get_user_keys(downapp_id)
|
||||
if not keys.get("success"):
|
||||
return keys
|
||||
|
||||
# Check if group exists locally (optional, or just call vendor)
|
||||
# For user proxy, we might not have local group_id yet if this is the first step.
|
||||
# The user provides group_id? No, user gets group_id after verification.
|
||||
# Wait, the prompt says "按照groupid上传素材" for step 3.
|
||||
# Step 2 is "真人认证". The user calls this to get the H5 link.
|
||||
# So we call rl_create_validate_session with the user's keys.
|
||||
|
||||
result = await rl_create_validate_session(
|
||||
org_id, keys.get("vendor", "volcengine"),
|
||||
keys["callback_url"], project_name,
|
||||
apikey=keys["ak"], secretkey=keys["sk"],
|
||||
user_id=downapp_id
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
async def rl_upload_user(org_id, group_id, source_url, asset_type, name, downapp_id):
|
||||
"""User proxy: Check app -> Get keys -> Upload asset."""
|
||||
keys = await _get_user_keys(downapp_id)
|
||||
if not keys.get("success"):
|
||||
return keys
|
||||
|
||||
# We need the vendor from the keys or the record
|
||||
# _get_user_keys returns the record, but I only extracted ak/sk.
|
||||
# Let's fetch vendor too.
|
||||
vendor = "volcengine" # Default or fetch from record
|
||||
# Refetch to get vendor
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_app_user", {"downapp_id": downapp_id})
|
||||
if recs:
|
||||
vendor = recs[0].vendor
|
||||
|
||||
result = await rl_create_asset(
|
||||
org_id, group_id, source_url, asset_type, name,
|
||||
vendor=vendor, apikey=keys["ak"], secretkey=keys["sk"],
|
||||
user_id=downapp_id
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
async def rl_sync_asset_status_user(org_id, asset_id, downapp_id):
|
||||
"""User proxy: Check app -> Get keys -> Sync status."""
|
||||
keys = await _get_user_keys(downapp_id)
|
||||
if not keys.get("success"):
|
||||
return keys
|
||||
|
||||
vendor = "volcengine"
|
||||
dbname = _get_dbname()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_app_user", {"downapp_id": downapp_id})
|
||||
if recs:
|
||||
vendor = recs[0].vendor
|
||||
|
||||
result = await rl_sync_asset_status(
|
||||
asset_id, vendor=vendor,
|
||||
apikey=keys["ak"], secretkey=keys["sk"]
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Module loader
|
||||
# ============================================================
|
||||
@ -402,4 +518,11 @@ def load_reallife_asset():
|
||||
g.rl_delete_group = rl_delete_group
|
||||
g.rl_sync_group_from_vendor = rl_sync_group_from_vendor
|
||||
g.rl_sync_assets_from_vendor = rl_sync_assets_from_vendor
|
||||
|
||||
# Downapp user APIs
|
||||
g.rl_apply = rl_apply
|
||||
g.rl_verify_user = rl_verify_user
|
||||
g.rl_upload_user = rl_upload_user
|
||||
g.rl_sync_asset_status_user = rl_sync_asset_status_user
|
||||
|
||||
return True
|
||||
|
||||
@ -46,6 +46,17 @@ paths_logined = [
|
||||
"/reallife_asset/api/sync_assets.dspy",
|
||||
"/reallife_asset/api/get_rl_asset_group_list.dspy",
|
||||
"/reallife_asset/api/get_rl_asset_list.dspy",
|
||||
# Downapp user APIs
|
||||
"/reallife_asset/api/rl_apply.dspy",
|
||||
"/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",
|
||||
]
|
||||
|
||||
def run_set_perm(role, path):
|
||||
|
||||
32
wwwroot/api/rl_app_user_create.dspy
Normal file
32
wwwroot/api/rl_app_user_create.dspy
Normal file
@ -0,0 +1,32 @@
|
||||
# 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}
|
||||
9
wwwroot/api/rl_app_user_delete.dspy
Normal file
9
wwwroot/api/rl_app_user_delete.dspy
Normal file
@ -0,0 +1,9 @@
|
||||
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}
|
||||
31
wwwroot/api/rl_app_user_update.dspy
Normal file
31
wwwroot/api/rl_app_user_update.dspy
Normal file
@ -0,0 +1,31 @@
|
||||
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}
|
||||
11
wwwroot/api/rl_apply.dspy
Normal file
11
wwwroot/api/rl_apply.dspy
Normal file
@ -0,0 +1,11 @@
|
||||
downapp_id = params_kw.get('downapp_id', '')
|
||||
vendor = params_kw.get('vendor', 'volcengine')
|
||||
callback_url = params_kw.get('callback_url', '')
|
||||
|
||||
if not downapp_id:
|
||||
return {"success": False, "message": "downapp_id 不能为空"}
|
||||
if not callback_url:
|
||||
return {"success": False, "message": "callback_url 不能为空"}
|
||||
|
||||
result = await rl_apply((await get_userorgid()) or '0', downapp_id, vendor, callback_url)
|
||||
return result
|
||||
16
wwwroot/api/rl_check_app_status.dspy
Normal file
16
wwwroot/api/rl_check_app_status.dspy
Normal file
@ -0,0 +1,16 @@
|
||||
downapp_id = params_kw.get('downapp_id', '')
|
||||
vendor = params_kw.get('vendor', 'volcengine')
|
||||
|
||||
if not downapp_id:
|
||||
return {"success": False, "message": "downapp_id 不能为空"}
|
||||
|
||||
keys = await _get_user_keys(downapp_id, vendor)
|
||||
if keys.get("success"):
|
||||
# Get full record info
|
||||
dbname = get_module_dbname('reallife_asset')
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.R("rl_app_user", {"downapp_id": downapp_id, "vendor": vendor})
|
||||
if recs:
|
||||
return {"success": True, "status": recs[0].status, "id": recs[0].id, "callback_url": recs[0].callback_url}
|
||||
return keys
|
||||
8
wwwroot/api/rl_status.dspy
Normal file
8
wwwroot/api/rl_status.dspy
Normal file
@ -0,0 +1,8 @@
|
||||
asset_id = params_kw.get('asset_id', '')
|
||||
downapp_id = params_kw.get('downapp_id', '')
|
||||
|
||||
if not asset_id or not downapp_id:
|
||||
return {"success": False, "message": "参数缺失"}
|
||||
|
||||
result = await rl_sync_asset_status_user((await get_userorgid()) or '0', asset_id, downapp_id)
|
||||
return result
|
||||
11
wwwroot/api/rl_upload.dspy
Normal file
11
wwwroot/api/rl_upload.dspy
Normal file
@ -0,0 +1,11 @@
|
||||
group_id = params_kw.get('group_id', '')
|
||||
source_url = params_kw.get('source_url', '')
|
||||
asset_type = params_kw.get('asset_type', 'Image')
|
||||
name = params_kw.get('name', '')
|
||||
downapp_id = params_kw.get('downapp_id', '')
|
||||
|
||||
if not group_id or not source_url or not downapp_id:
|
||||
return {"success": False, "message": "参数缺失"}
|
||||
|
||||
result = await rl_upload_user((await get_userorgid()) or '0', group_id, source_url, asset_type, name, downapp_id)
|
||||
return result
|
||||
11
wwwroot/api/rl_verify.dspy
Normal file
11
wwwroot/api/rl_verify.dspy
Normal file
@ -0,0 +1,11 @@
|
||||
group_id = params_kw.get('group_id', '') # Optional, if re-creating
|
||||
downapp_id = params_kw.get('downapp_id', '')
|
||||
project_name = params_kw.get('project_name', 'default')
|
||||
|
||||
if not downapp_id:
|
||||
return {"success": False, "message": "downapp_id 不能为空"}
|
||||
|
||||
# If group_id is provided, we might be refreshing the link for an existing group?
|
||||
# For simplicity, always create new session for user.
|
||||
result = await rl_verify_user((await get_userorgid()) or '0', group_id, downapp_id, project_name)
|
||||
return result
|
||||
Loading…
x
Reference in New Issue
Block a user