From 5aa0fe4303af991bb0b7f3584dd042723bf20f49 Mon Sep 17 00:00:00 2001 From: ping <1017253325@qq.com> Date: Wed, 20 May 2026 19:10:26 +0800 Subject: [PATCH] update --- b/cntoai/create_model_apikey.dspy | 91 +++++++++++++++++++++++++++ b/cntoai/get_model_apikey.dspy | 85 +++++++++++++++++++++++++ b/cntoai/model_management_add.dspy | 49 +++++++++++++++ b/cntoai/model_management_detail.dspy | 47 ++++++++++++++ b/cntoai/model_management_list.dspy | 25 ++++++++ b/cntoai/model_management_search.dspy | 80 +++++++++++++++++++++++ b/cntoai/model_management_unlist.dspy | 25 ++++++++ b/cntoai/model_management_update.dspy | 45 +++++++++++++ b/cntoai/process_user_billing.dspy | 40 +++++++++--- 9 files changed, 477 insertions(+), 10 deletions(-) create mode 100644 b/cntoai/create_model_apikey.dspy create mode 100644 b/cntoai/get_model_apikey.dspy create mode 100644 b/cntoai/model_management_add.dspy create mode 100644 b/cntoai/model_management_detail.dspy create mode 100644 b/cntoai/model_management_list.dspy create mode 100644 b/cntoai/model_management_search.dspy create mode 100644 b/cntoai/model_management_unlist.dspy create mode 100644 b/cntoai/model_management_update.dspy diff --git a/b/cntoai/create_model_apikey.dspy b/b/cntoai/create_model_apikey.dspy new file mode 100644 index 0000000..5dd88ed --- /dev/null +++ b/b/cntoai/create_model_apikey.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/get_model_apikey.dspy b/b/cntoai/get_model_apikey.dspy new file mode 100644 index 0000000..5fea137 --- /dev/null +++ b/b/cntoai/get_model_apikey.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_add.dspy b/b/cntoai/model_management_add.dspy new file mode 100644 index 0000000..3f0a114 --- /dev/null +++ b/b/cntoai/model_management_add.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_detail.dspy b/b/cntoai/model_management_detail.dspy new file mode 100644 index 0000000..69448ec --- /dev/null +++ b/b/cntoai/model_management_detail.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_list.dspy b/b/cntoai/model_management_list.dspy new file mode 100644 index 0000000..6789f82 --- /dev/null +++ b/b/cntoai/model_management_list.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_search.dspy b/b/cntoai/model_management_search.dspy new file mode 100644 index 0000000..18ad880 --- /dev/null +++ b/b/cntoai/model_management_search.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_unlist.dspy b/b/cntoai/model_management_unlist.dspy new file mode 100644 index 0000000..8900dda --- /dev/null +++ b/b/cntoai/model_management_unlist.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/model_management_update.dspy b/b/cntoai/model_management_update.dspy new file mode 100644 index 0000000..854de52 --- /dev/null +++ b/b/cntoai/model_management_update.dspy @@ -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 \ No newline at end of file diff --git a/b/cntoai/process_user_billing.dspy b/b/cntoai/process_user_billing.dspy index 5cb17c1..1036eaa 100644 --- a/b/cntoai/process_user_billing.dspy +++ b/b/cntoai/process_user_billing.dspy @@ -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