This commit is contained in:
ping 2026-05-20 19:10:26 +08:00
parent 0b7c9088ad
commit 5aa0fe4303
9 changed files with 477 additions and 10 deletions

View File

@ -0,0 +1,91 @@
async def create_model_apikey(ns={}):
import aiohttp
if not ns.get('userid'):
ns['userid'] = await get_user()
if not ns.get('userid'):
return {
'status': False,
'msg': '未找到用户'
}
# 通过userid从user_api_keys表中查询opc_apikey
db = DBPools()
async with db.sqlorContext('kboss') as sor:
records = await sor.R('user_api_keys', {'userid': ns['userid'], 'action': 'sync'})
if not records:
return {
'status': False,
'msg': '未找到用户opc_apikey'
}
already_sync_user_key = records[0]['opc_apikey']
already_sync_user_appid = records[0]['appid']
# 目标URL
base_url = 'https://ai.atvoe.com'
url = f"{base_url}/dapi/apply_apikey.dspy"
# 请求头
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer %s" % already_sync_user_key
}
# 请求体数据
payload = {
"appname": "cn_ai_user",
"description": "",
}
try:
# 创建一个异步会话
result_sysnc = None
async with aiohttp.ClientSession() as session:
# 发送POST请求
async with session.post(url, headers=headers, data=json.dumps(payload)) as response:
# 打印响应状态码
debug(f"create_model_apikey状态码: {response.status}")
result_sysnc = await response.json()
if not result_sysnc.get('status') == 'success':
debug(f"create_model_apikey创建模型apikey失败: {result_sysnc}")
return {
'status': False,
'msg': f"创建模型apikey失败: {result_sysnc}"
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# user_api_keys表格 userid/opc_apikey
# 首先判断apikey是否存在
apikey = result_sysnc['data'][0].get('apikey')
appid = result_sysnc['data'][0].get('appid')
secretkey = result_sysnc['data'][0].get('secretkey')
await sor.C('user_api_keys', {
'userid': userid,
'opc_apikey': apikey,
'appid': appid,
'secretkey': secretkey,
'action': 'user_self_create',
'expire_time': None,
})
debug(f"sync_cn_ai_user用户{payload['user']['id']}同步成功")
return {
'status': True,
'msg': '用户同步成功'
}
except Exception as e:
debug(f"sync_cn_ai_user{userid}同步用户失败: {e}")
return {
'status': False,
'msg': f"sync_cn_ai_user{userid}同步用户失败: {e}"
}
ret = await create_model_apikey(params_kw)
return ret

View File

@ -0,0 +1,85 @@
async def get_model_apikey(ns={}):
import aiohttp
if not ns.get('userid'):
ns['userid'] = await get_user()
if not ns.get('userid'):
return {
'status': False,
'msg': '未找到用户'
}
# 通过userid从user_api_keys表中查询opc_apikey
db = DBPools()
async with db.sqlorContext('kboss') as sor:
records = await sor.R('user_api_keys', {'userid': ns['userid'], 'action': 'sync'})
if not records:
return {
'status': False,
'msg': '未找到用户opc_apikey'
}
already_sync_user_key = records[0]['opc_apikey']
already_sync_user_appid = records[0]['appid']
# 目标URL
base_url = 'https://ai.atvoe.com'
url = f"{base_url}/dapi/apply_apikey.dspy"
# 请求头
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer %s" % already_sync_user_key
}
try:
# 创建一个异步会话
result_sysnc = None
async with aiohttp.ClientSession() as session:
# 发送GET请求
async with session.get(url, headers=headers) as response:
# 打印响应状态码
debug(f"get_model_apikey状态码: {response.status}")
result_sysnc = await response.json()
if not result_sysnc.get('status') == 'success':
debug(f"get_model_apikey获取模型apikey失败: {result_sysnc}")
return {
'status': False,
'msg': f"获取模型apikey失败: {result_sysnc}"
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# user_api_keys表格 userid/opc_apikey
# 首先判断apikey是否存在
apikey = result_sysnc['data'][0].get('apikey')
appid = result_sysnc['data'][0].get('appid')
secretkey = result_sysnc['data'][0].get('secretkey')
await sor.C('user_api_keys', {
'userid': userid,
'opc_apikey': apikey,
'appid': appid,
'secretkey': secretkey,
'action': 'user_self_create',
'expire_time': None,
})
debug(f"sync_cn_ai_user用户{payload['user']['id']}同步成功")
return {
'status': True,
'msg': '用户同步成功'
}
except Exception as e:
debug(f"sync_cn_ai_user{userid}同步用户失败: {e}")
return {
'status': False,
'msg': f"sync_cn_ai_user{userid}同步用户失败: {e}"
}
ret = await get_model_apikey(params_kw)
return ret

View File

@ -0,0 +1,49 @@
# 可写入/更新的字段(不含 id、created_at、updated_at
_MODEL_FIELDS = (
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
'context_length', 'input_token_price', 'output_token_price',
'cache_hit_input_price', 'billing_method', 'billing_unit',
'capabilities', 'limitations', 'highlights', 'is_active',
'description', 'listing_status',
)
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
def _build_model_dict(ns, include_listing_status=False):
data = {}
for field in _MODEL_FIELDS:
if field in ns and ns.get(field) is not None and ns.get(field) != '':
data[field] = ns.get(field)
if include_listing_status and 'listing_status' not in data:
data['listing_status'] = ns.get('listing_status', 0)
return data
async def model_management_add(ns={}):
"""新增模型,默认待上架 listing_status=0"""
if not ns.get('provider') or not ns.get('model_name'):
return {'status': False, 'msg': 'provider and model_name are required'}
ns_dic = _build_model_dict(ns, include_listing_status=True)
if 'listing_status' not in ns_dic:
ns_dic['listing_status'] = 0
if 'is_active' not in ns_dic:
ns_dic['is_active'] = 1
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.C('model_management', ns_dic)
return {'status': True, 'msg': 'create model success', 'data': ns_dic}
except Exception as e:
await sor.rollback()
return {'status': False, 'msg': 'create model failed, %s' % str(e)}
ret = await model_management_add(params_kw)
return ret

View File

@ -0,0 +1,47 @@
# 可写入/更新的字段(不含 id、created_at、updated_at
_MODEL_FIELDS = (
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
'context_length', 'input_token_price', 'output_token_price',
'cache_hit_input_price', 'billing_method', 'billing_unit',
'capabilities', 'limitations', 'highlights', 'is_active',
'description', 'listing_status',
)
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
def _build_model_dict(ns, include_listing_status=False):
data = {}
for field in _MODEL_FIELDS:
if field in ns and ns.get(field) is not None and ns.get(field) != '':
data[field] = ns.get(field)
if include_listing_status and 'listing_status' not in data:
data['listing_status'] = ns.get('listing_status', 0)
return data
async def model_management_detail(ns={}):
"""根据 id 获取单条模型(编辑页回显)"""
model_id = ns.get('id')
if not model_id:
return {'status': False, 'msg': 'id is required'}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
find_sql = "SELECT * FROM model_management WHERE id = '%s' LIMIT 1;" % _escape(model_id)
result = await sor.sqlExe(find_sql, {})
if not result:
return {'status': False, 'msg': 'model not found'}
return {
'status': True,
'msg': 'get model detail success',
'data': result[0],
}
except Exception as e:
return {'status': False, 'msg': 'get model detail failed, %s' % str(e)}
ret = await model_management_detail(params_kw)
return ret

View File

@ -0,0 +1,25 @@
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
async def model_management_list(ns={}):
"""上架listing_status 置为 1"""
model_id = ns.get('id')
if not model_id:
return {'status': False, 'msg': 'id is required'}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """
UPDATE model_management SET listing_status = 1 WHERE id = '%s';
""" % _escape(model_id)
await sor.sqlExe(update_sql, {})
return {'status': True, 'msg': 'model listed success'}
except Exception as e:
await sor.rollback()
return {'status': False, 'msg': 'model list failed, %s' % str(e)}
ret = await model_management_list(params_kw)
return ret

View File

@ -0,0 +1,80 @@
# 可写入/更新的字段(不含 id、created_at、updated_at
_MODEL_FIELDS = (
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
'context_length', 'input_token_price', 'output_token_price',
'cache_hit_input_price', 'billing_method', 'billing_unit',
'capabilities', 'limitations', 'highlights', 'is_active',
'description', 'listing_status',
)
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
def _build_model_dict(ns, include_listing_status=False):
data = {}
for field in _MODEL_FIELDS:
if field in ns and ns.get(field) is not None and ns.get(field) != '':
data[field] = ns.get(field)
if include_listing_status and 'listing_status' not in data:
data['listing_status'] = ns.get('listing_status', 0)
return data
async def model_management_search(ns={}):
"""
分页查询模型列表,支持按 model_name / model_type / provider 筛选。
返回模型总数、待上架总数、已上架总数。
"""
import traceback
page_size = int(ns.get('page_size', 100))
current_page = int(ns.get('current_page', 1))
offset = (current_page - 1) * page_size
conditions = ['1=1']
if ns.get('model_name'):
model_name = ns.get('model_name')
conditions.append("model_name LIKE '%s'" % model_name)
if ns.get('model_type'):
conditions.append("model_type = '%s'" % _escape(ns.get('model_type')))
if ns.get('provider'):
conditions.append("provider = '%s'" % _escape(ns.get('provider')))
if ns.get('listing_status') is not None and ns.get('listing_status') != '':
conditions.append("listing_status = '%s'" % _escape(ns.get('listing_status')))
where_clause = ' AND '.join(conditions)
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
stats_sql = """SELECT COUNT(*) AS total_count, SUM(CASE WHEN listing_status = 0 THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN listing_status = 1 THEN 1 ELSE 0 END) AS listed_count FROM model_management;"""
stats_li = await sor.sqlExe(stats_sql, {})
stats = stats_li[0] if stats_li else {}
count_sql = """SELECT COUNT(*) AS total_count FROM model_management WHERE %s;""" % where_clause
filter_total = (await sor.sqlExe(count_sql, {}))[0]['total_count']
find_sql = """SELECT * FROM model_management WHERE %s ORDER BY updated_at DESC LIMIT %s OFFSET %s;""" % (where_clause, page_size, offset)
model_list = await sor.sqlExe(find_sql, {})
return {
'status': True,
'msg': 'search model success',
'data': {
'total_count': stats.get('total_count', 0),
'pending_count': int(stats.get('pending_count') or 0),
'listed_count': int(stats.get('listed_count') or 0),
'filter_total': filter_total,
'page_size': page_size,
'current_page': current_page,
'model_list': model_list,
},
}
except Exception as e:
return {'status': False, 'msg': 'search model failed, %s' % traceback.format_exc()}
ret = await model_management_search(params_kw)
return ret

View File

@ -0,0 +1,25 @@
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
async def model_management_unlist(ns={}):
"""下架listing_status 置为 0统计归入待上架"""
model_id = ns.get('id')
if not model_id:
return {'status': False, 'msg': 'id is required'}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """
UPDATE model_management SET listing_status = 0 WHERE id = '%s';
""" % _escape(model_id)
await sor.sqlExe(update_sql, {})
return {'status': True, 'msg': 'model unlisted success'}
except Exception as e:
await sor.rollback()
return {'status': False, 'msg': 'model unlist failed, %s' % str(e)}
ret = await model_management_unlist(params_kw)
return ret

View File

@ -0,0 +1,45 @@
# 可写入/更新的字段(不含 id、created_at、updated_at
_MODEL_FIELDS = (
'llmid', 'provider', 'model_name', 'display_name', 'model_type',
'context_length', 'input_token_price', 'output_token_price',
'cache_hit_input_price', 'billing_method', 'billing_unit',
'capabilities', 'limitations', 'highlights', 'is_active',
'description', 'listing_status',
)
def _escape(value):
if value is None:
return None
return str(value).replace("'", "''")
def _build_model_dict(ns, include_listing_status=False):
data = {}
for field in _MODEL_FIELDS:
if field in ns and ns.get(field) is not None and ns.get(field) != '':
data[field] = ns.get(field)
if include_listing_status and 'listing_status' not in data:
data['listing_status'] = ns.get('listing_status', 0)
return data
async def model_management_update(ns={}):
"""编辑模型id 必传"""
model_id = ns.get('id')
if not model_id:
return {'status': False, 'msg': 'id is required'}
ns_dic = _build_model_dict(ns)
ns_dic['id'] = model_id
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.U('model_management', ns_dic)
return {'status': True, 'msg': 'update model success'}
except Exception as e:
await sor.rollback()
return {'status': False, 'msg': 'update model failed, %s' % str(e)}
ret = await model_management_update(params_kw)
return ret

View File

@ -228,8 +228,15 @@ async def process_user_billing(ns={}):
providername = ns.get('providername')
productname = ns.get('productname')
amount = ns.get('amount')
use_saleprotocol = ns.get('use_saleprotocol', False)
use_saleprotocol = ns.get('use_saleprotocol', True)
quantity = int(ns.get('quantity', 1))
llmid = ns.get('llmid')
if not llmid:
return {
'status': False,
'msg': 'llmid必传'
}
try:
amount = round(float(amount), 2)
@ -241,9 +248,22 @@ async def process_user_billing(ns={}):
db = DBPools()
async with db.sqlorContext('kboss') as sor:
provider_list = await sor.R('provider', {'name': providername})
if not provider_list:
return {'status': False, 'msg': '厂商不存在 %s' % providername}
product_li = await sor.R('product', {'providerpid': llmid, 'del_flg': '0'})
if not product_li:
return {
'status': False,
'msg': '未找到对应产品,请确认'
}
product = product_li[0]
productname = product['name']
providerid = product['providerid']
providername_list = await sor.R('organization', {'id': providerid})
if not providername_list:
return {
'status': False,
'msg': '厂商不存在 %s' % providername
}
providername = providername_list[0]['orgname']
userid_li = await sor.R('user_api_keys', {'opc_apikey': apikey})
if not userid_li:
@ -262,12 +282,12 @@ async def process_user_billing(ns={}):
return {'status': False, 'msg': '用户所属机构不存在'}
customerid = org_list[0]['id']
product = await _lookup_product(sor, providername, productname)
if not product:
return {
'status': False,
'msg': '未找到对应产品,请确认 providername/productname 与库中 provider、product 配置一致',
}
# product = await _lookup_product(sor, providername, productname)
# if not product:
# return {
# 'status': False,
# 'msg': '未找到对应产品,请确认 providername/productname 与库中 provider、product 配置一致',
# }
list_price = amount
unit_price = amount