Merge branch 'main' of git.opencomputing.cn:yumoqing/kboss

This commit is contained in:
hrx 2026-07-22 11:34:21 +08:00
commit 2189cf34fd
6 changed files with 393 additions and 15 deletions

View File

@ -35,6 +35,8 @@ async def get_user_balance(ns={}):
} }
orgid = await sor.R('organization', {'id': user[0]['orgid']}) orgid = await sor.R('organization', {'id': user[0]['orgid']})
balance = await getCustomerBalance(sor, orgid[0]['id']) balance = await getCustomerBalance(sor, orgid[0]['id'])
ns['balance'] = balance
debug(ns)
return { return {
'status': 'ok', 'status': 'ok',
'balance': balance 'balance': balance

View File

@ -1,6 +1,12 @@
async def sync_model_to_llm(ns={}): async def sync_model_to_llm(ns={}):
import aiohttp import aiohttp
async def get_sync_orgid(sor):
if ns.get('orgid'):
return ns.get('orgid')
user_li = await sor.R('users', {'id': await get_user()})
return user_li[0].get('orgid') if user_li else ''
# 从数据库读取domain和Bearer token # 从数据库读取domain和Bearer token
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
@ -13,6 +19,13 @@ async def sync_model_to_llm(ns={}):
} }
domain = domain_li[0]['pvalue'] domain = domain_li[0]['pvalue']
bearer_token = user_key[0]['pvalue'] bearer_token = user_key[0]['pvalue']
# sync_orgid = await get_sync_orgid(sor)
sync_orgid = "mIWUHBeeDM8mwAFPIQ8pS" # 固定写
if not sync_orgid:
return {
'status': False,
'msg': '未找到当前用户所属机构'
}
url = f"{domain}/llmage/list_llms" url = f"{domain}/llmage/list_llms"
header = { header = {
@ -33,29 +46,103 @@ async def sync_model_to_llm(ns={}):
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
new_llms_count = 0 new_llms_count = 0
sync_products_count = 0
new_llms_list = [] new_llms_list = []
for category_list in result: for category_list in result:
for item in category_list.get('llms', []): for item in category_list.get('llms', []):
provider_orgcode = item.get('providerid')
provider_name = item.get('provider_name') or item.get('providerid') or '模型供应商'
if not provider_orgcode:
continue
provider_ns = {
'orgid': sync_orgid,
'orgcode': provider_orgcode,
'orgname': provider_name,
'name': provider_name,
'description': provider_name,
'appid': item.get('upappid') or '',
'providerkey': provider_orgcode,
'state': '1',
'start_date': '2026-07-01',
'end_date': '9999-12-31',
'settle_flowid': '0',
'salemode': '0',
'settle_mode': '3',
'settle_datep': 'M00',
'contactor': '连战',
'contactor_phone': '13800138000',
'emailaddress': 'lianzhan@cntoai.com',
'address': '北京市海淀区',
'org_type': '4'
}
provider_res = await path_call('../provider/providerAdd.dspy', provider_ns)
if not provider_res.get('status'):
return {
'status': False,
'msg': '供应商同步失败, %s, %s' % (provider_name, provider_res.get('msg'))
}
provider_data = provider_res.get('data') or {}
provider_orgid = provider_data.get('provider_orgid') or provider_data.get('orgid')
protocolid = provider_data.get('protocolid')
if not provider_orgid or not protocolid:
return {
'status': False,
'msg': '供应商同步失败, 未返回provider_orgid或protocolid, %s' % provider_name
}
product_ns = {
'providerid': provider_orgid,
'providerpid': item.get('id'),
'protocolid': protocolid,
'name': item.get('name'),
'product_code': item.get('model'),
'reseller_orgid': sync_orgid,
'label': item.get('catelogname') or category_list.get('catelogname'),
'classify': 'A',
'ptype': '21',
'unit': 'hour',
'discount': '1',
'product_area': '北京'
}
product_res = await path_call('../product/productAdd.dspy', product_ns)
if not product_res.get('status'):
return {
'status': False,
'msg': '产品同步失败, %s, %s' % (item.get('name'), product_res.get('msg'))
}
sync_products_count += 1
# 查找数据库中是否已经存在,不存在就插入 # 查找数据库中是否已经存在,不存在就插入
exist_llm = await sor.R('llm', {'model': item.get('model')}) exist_llm = await sor.R('llm', {'model': item.get('model')})
if exist_llm: if exist_llm:
# 更新
exits_id = exist_llm[0].get('id')
await sor.U('llm', {'id': exits_id}, item)
continue continue
new_llms = { new_llms = {
'id': item.get('id'), 'id': item.get('id'),
'name': item.get('name'), 'name': item.get('name'),
'model': item.get('model'), 'model': item.get('model'),
'description': item.get('description'), 'description': item.get('description'),
'llmcatelogid': item.get('catelog_id') or item.get('catelogid'),
'iconid': item.get('iconid'), 'iconid': item.get('iconid'),
'upappid': item.get('upappid'), 'upappid': item.get('upappid'),
'apiname': item.get('apiname'),
'providerid': item.get('providerid'), 'providerid': item.get('providerid'),
'ownerid': item.get('ownerid') or '0', 'ownerid': item.get('ownerid'),
'enabled_date': item.get('enabled_date'), 'enabled_date': item.get('enabled_date'),
'expired_date': item.get('expired_date'), 'expired_date': item.get('expired_date'),
'query_apiname': item.get('query_apiname') or '', 'min_balance': item.get('min_balance'),
'status': item.get('status'),
'catelogname': item.get('catelogname'),
'catelog_id': item.get('catelog_id'),
'apiname': item.get('apiname'),
'query_apiname': item.get('query_apiname'),
'query_period': item.get('query_period'), 'query_period': item.get('query_period'),
'ppid': item.get('ppid'), 'ppid': item.get('ppid'),
'provider_name': item.get('provider_name'),
'pricing_display': item.get('pricing_display'),
'provider_iconurl': item.get('provider_iconurl')
} }
new_llms_count += 1 new_llms_count += 1
new_llms_list.append(new_llms.get('model')) new_llms_list.append(new_llms.get('model'))
@ -63,7 +150,7 @@ async def sync_model_to_llm(ns={}):
return { return {
'status': True, 'status': True,
'msg': f"sync_llm_list同步模型成功共插入{new_llms_count}个模型,模型列表: {new_llms_list}" 'msg': f"sync_llm_list同步模型成功同步{sync_products_count}个产品,共插入{new_llms_count}个模型,模型列表: {new_llms_list}"
} }
except Exception as e: except Exception as e:

145
b/llm.sql Normal file
View File

@ -0,0 +1,145 @@
# 创建表格命令
CREATE TABLE llm (
id VARCHAR(64) NOT NULL COMMENT '唯一标识',
name VARCHAR(50) NOT NULL COMMENT '模型名称',
model VARCHAR(50) NOT NULL COMMENT '模型代号',
description TEXT COMMENT '模型描述',
iconid VARCHAR(50) COMMENT '图标标识',
upappid VARCHAR(50) COMMENT '上层应用ID',
providerid VARCHAR(64) COMMENT '供应商ID',
ownerid VARCHAR(20) DEFAULT '0' COMMENT '所有者ID',
enabled_date DATE COMMENT '启用日期',
expired_date DATE COMMENT '过期日期',
min_balance INT DEFAULT 0 COMMENT '最低余额要求(元)',
status VARCHAR(20) COMMENT '状态如published',
catelogname VARCHAR(50) COMMENT '目录名称(如文生视频)',
catelog_id VARCHAR(20) COMMENT '目录ID',
apiname VARCHAR(50) COMMENT 'API名称',
query_apiname VARCHAR(50) COMMENT '查询API名称',
query_period INT COMMENT '查询周期(秒)',
ppid VARCHAR(64) COMMENT '定价计划ID',
provider_name VARCHAR(100) COMMENT '供应商名称',
pricing_display TEXT COMMENT '定价显示文本',
provider_iconurl VARCHAR(255) COMMENT '供应商图标URL',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='LLM模型信息表';
# 厂商返回的部分数据示例
[
{
"catelogid": "2i2v",
"catelogname": "首尾帧生视频",
"llms": [
{
"id": "r0ill7L0s6PtBS8mkmg-7",
"name": "viduq3-pro",
"model": "viduq3-pro",
"description": "vidu文生视频,viduq3-pro模型",
"iconid": "vidu",
"upappid": "vidu",
"providerid": "Bu3Z6OXANehbkD6OK5NJb",
"ownerid": "0",
"enabled_date": "2026-01-01",
"expired_date": "9999-12-31",
"min_balance": 10,
"status": "published",
"catelogname": "首尾帧生视频",
"catelog_id": "2i2v",
"apiname": "2i2v",
"query_apiname": "taskStatus",
"query_period": 30,
"ppid": "sF2gcl7UeANKtnZv8hfvL",
"provider_name": "北京生数科技有限公司",
"pricing_display": " - duration: 0.56 元/秒 ",
"provider_iconurl": "https://token.opencomputing.cn/appbase/show_icon.dspy?id=vidu"
}
]
},
{
"catelogid": "asr",
"catelogname": "语音识别",
"llms": [
{
"id": "9glv3S-JSzxholxAblU14",
"name": "qwen3-asr-flash",
"model": "qwen3-asr-flash",
"description": "通义千问系列的录音文件识",
"iconid": "qwen",
"upappid": "tongyi-wan",
"providerid": "6fadgewjraOyvxC_EkHou",
"ownerid": "0",
"enabled_date": "2025-12-01",
"expired_date": "9999-12-31",
"min_balance": 10,
"status": "published",
"catelogname": "语音识别",
"catelog_id": "asr",
"apiname": "short_asr",
"query_apiname": null,
"query_period": null,
"ppid": "16pwEsQeHiOrxC0O6A2j6",
"provider_name": "阿里云",
"provider_iconurl": "https://token.opencomputing.cn/appbase/show_icon.dspy?id=qwen"
}
]
},
{
"catelogid": "digital_human",
"catelogname": "数字人",
"llms": [
{
"id": "KOsAo6YjTdU0KG7_sKUJF",
"name": "wan2.2-s2v",
"model": "wan2.2-s2v",
"description": "模型wan2.2-s2v输入音频和照片用照片中的人生成数字人",
"iconid": "qwen",
"upappid": "tongyi-wan",
"providerid": "6fadgewjraOyvxC_EkHou",
"ownerid": "0",
"enabled_date": "2025-12-19",
"expired_date": "9999-12-31",
"min_balance": 10,
"status": "published",
"catelogname": "数字人",
"catelog_id": "digital_human",
"apiname": "avator",
"query_apiname": "avatorstatus",
"query_period": 10,
"ppid": "0B6aldoAej1PpZ4ydtrEZ",
"provider_name": "阿里云",
"pricing_display": " - duration: 0.5 元/秒 [分辨率=480P]\n - duration: 0.9 元/秒 [分辨率=720P]",
"provider_iconurl": "https://token.opencomputing.cn/appbase/show_icon.dspy?id=qwen"
}
]
},
{
"catelogid": "i2v",
"catelogname": "图生视频",
"llms": [
{
"id": "7y7kfYM0Xasizyi37WvAZ",
"name": "happyhorse-1.0-i2v",
"model": "happyhorse-1.0-i2v",
"description": "HappyHorse图生视频模型以首帧图片为基础支持通过文本描述进行引导生成物理真实、运动流畅的视频。",
"iconid": "qwen",
"upappid": "tongyi-wan",
"providerid": "6fadgewjraOyvxC_EkHou",
"ownerid": "0",
"enabled_date": "2026-04-28",
"expired_date": "9999-12-31",
"min_balance": 10,
"status": "published",
"catelogname": "图生视频",
"catelog_id": "i2v",
"apiname": "i2v-happyhorse",
"query_apiname": "t2vstatus",
"query_period": 10,
"ppid": "knEwDP35DEz5oMAXyvwN3",
"provider_name": "阿里云",
"pricing_display": " - duration: 0.9 元/秒 [SR=720]\n - duration: 1.6 元/秒 [SR=1080]",
"provider_iconurl": "https://token.opencomputing.cn/appbase/show_icon.dspy?id=qwen"
},
]
}
]

View File

@ -82,14 +82,6 @@ async def providerAdd(ns={}):
'status': False, 'status': False,
'msg': 'provider add failed, orgcode is empty' 'msg': 'provider add failed, orgcode is empty'
} }
# filter by orgcode
res = await sor.R('organization', {})
orgcodes = [orgc.get('orgcode') for orgc in res]
if org_code in orgcodes:
return {
'status': False,
'msg': 'provider add failed, orgcode has already exist'
}
if not ns.get('orgid'): if not ns.get('orgid'):
orgid_li = await sor.R('users', {'id': await get_user()}) orgid_li = await sor.R('users', {'id': await get_user()})
orgid = orgid_li[0].get('orgid') if orgid_li else '' orgid = orgid_li[0].get('orgid') if orgid_li else ''
@ -101,8 +93,33 @@ async def providerAdd(ns={}):
else: else:
orgid = ns.get('orgid') orgid = ns.get('orgid')
# filter by orgcode; existing provider returns ids for idempotent callers
exist_org_li = await sor.R('organization', {'orgcode': org_code, 'del_flg': '0'})
if len(exist_org_li) >= 1:
exist_org = exist_org_li[0]
exist_provider_li = await sor.R('provider', {'orgid': exist_org.get('id'), 'del_flg': '0'})
if not exist_provider_li:
return {
'status': False,
'msg': 'provider add failed, orgcode has already exist but provider not found'
}
exist_protocol_li = await sor.R('saleprotocol', {'offer_orgid': exist_org.get('id'), 'bid_orgid': orgid, 'del_flg': '0'})
if not exist_protocol_li:
exist_protocol_li = await sor.R('saleprotocol', {'offer_orgid': exist_org.get('id'), 'del_flg': '0'})
return {
'status': True,
'msg': 'provider already exist',
'data': {
'orgid': exist_org.get('id'),
'provider_orgid': exist_org.get('id'),
'provider_id': exist_provider_li[0].get('id'),
'protocolid': exist_protocol_li[0].get('id') if exist_protocol_li else None
}
}
# 供应商parentid 和 运营机构id相同 # 供应商parentid 和 运营机构id相同
ns_org['parentid'] = orgid ns_org['parentid'] = orgid
ns_sale_mode['bid_orgid'] = orgid
if not ns_provider.get('start_date'): if not ns_provider.get('start_date'):
ns_provider['start_date'] = time.strftime('%Y-%m-%d %H:%M:%S') ns_provider['start_date'] = time.strftime('%Y-%m-%d %H:%M:%S')
@ -115,7 +132,13 @@ async def providerAdd(ns={}):
await openProviderAccounts(sor, orgid, ns_org['id']) await openProviderAccounts(sor, orgid, ns_org['id'])
return { return {
"status": True, "status": True,
"msg": "provider add success" "msg": "provider add success",
"data": {
"orgid": ns_org['id'],
"provider_orgid": ns_org['id'],
"provider_id": ns_provider['id'],
"protocolid": ns_sale_mode['id']
}
} }
except Exception as e: except Exception as e:
raise e raise e

View File

@ -46,7 +46,7 @@ async def get_ipc_logo(ns={}):
if '?domain_name=' in url_link: if '?domain_name=' in url_link:
domain_url = url_link.split('?domain_name=')[1] domain_url = url_link.split('?domain_name=')[1]
# 如果是业主机构 # 如果是业主机构
if ('ncmatch' in domain_url or '9527' in domain_url or '8889' in domain_url or '8891' in domain_url or domain_url in ['xterm.kaiyuancloud.cn','www.kaiyuancloud.cn', 'dev.kaiyuancloud.cn', 'dev.opencomputing.cn', 'test.kaiyuancloud.cn', 'localhost', 'www.opencomputing.cn', 'opencomputing.cn']) and '/domain/' not in url_link: if ('ncmatch' in domain_url or 'zgcopc.opencomputing.cn' in domain_url or '9527' in domain_url or '8889' in domain_url or '8891' in domain_url or domain_url in ['xterm.kaiyuancloud.cn','www.kaiyuancloud.cn', 'dev.kaiyuancloud.cn', 'dev.opencomputing.cn', 'test.kaiyuancloud.cn', 'localhost', 'www.opencomputing.cn', 'opencomputing.cn']) and '/domain/' not in url_link:
yezhu_info = (await sor.R('organization', {'org_type': '0'}))[0] yezhu_info = (await sor.R('organization', {'org_type': '0'}))[0]
domain_res = (await sor.R('params', {'pname': '业主机构域名'}))[0]['pvalue'] domain_res = (await sor.R('params', {'pname': '业主机构域名'}))[0]['pvalue']
# yezhu = { # yezhu = {
@ -91,6 +91,37 @@ async def get_ipc_logo(ns={}):
] ]
} }
} }
elif 'zgcopc.opencomputing.cn' in domain_url or '9527' in domain_url:
yezhu['logo'] = "https://zgcopc.opencomputing.cn/idfile?path=logo_zgcopc.png"
yezhu['additional_msg'] = {
"home": {
"logoImg": "https://zgcopc.opencomputing.cn/idfile?path=logo_zgcopc.png",
"bannerTitle": "中关村国际孵化器",
"adress": "北京市海淀区上地信息路2号",
"footerTitle": "中关村国际孵化器有限公司",
'qrCode': 'https://zgcopc.opencomputing.cn/idfile?path=firstpagehot/ncmatch_inquiry.jpg',
'footer_info': '京ICP备13007859号-1 北京中关村国际孵化器有限公司',
"copyright": "",
"domain_name": "zgcopc.opencomputing.cn",
"email": "zhangjian@bjzai.org.cn",
"license": "京ICP备13007859号-1",
"logo": "https://zgcopc.opencomputing.cn/idfile?path=logo_zgcopc.png",
"mobile": "13601380912",
"publicsecurity": "11010502054007",
"businesslicense": "京ICP备13007859号-1"
},
"about": {
"title": "开元数智",
"logo": "https://www.opencomputing.cn/idfile?path=logo_ncmatch.png",
"content": [
"开元数智北京科技有限公司以下简称“开元数智”创立于2022年总部位于北京中关村科技园区现已形成以上海、南京、深圳、济南为区域枢纽的全国性服务网络。作为新型数字基础设施建设领域的创新企业公司主营业务涵盖智能算力中心规划建设与运营、算力网络体系构建、企业级AI算力服务及大模型应用整体解决方案。",
"公司自主研发的NCMatch智能算力公共服务平台作为行业领先的一站式人工智能算力服务解决方案已构建云网算数智“五位一体”的服务体系。该平台深度整合算力资源调度系统、智能算法模型库及行业应用矩阵为高等院校、科研院所、政企机构及人工智能开发企业提供专业级算力云服务全面覆盖基础算力供给、大模型训练调优及产业智能化升级等多维度需求。",
"开元数智作为国家算力网络建设的重要参与单位,已获得工信部、教育部等部委直属机构指导,并与中关村数智人工智能产业联盟、国家超级计算中心、南京未来网络研究院等权威机构建立战略合作关系。公司深度参与全国超算互联网及全国一体化算力网国家级重点工程,在算力基础设施建设领域具备成熟的技术积累和项目实施经验。",
"开元数智北京科技有限公司合作的客户有中国联通、中国电信、中国移动、未来网络、中信网络、国家超级计算济南中心、华为、阿里云、百度智能云、腾讯云、火山、智谱·AI、衔远科技、创新工场、竹间智能、深言科技、澜舟科技、商汤科技、MINMAX、云从科技、第四范式、深度求索、百川智能、Moonshot AI、昆仑万维、深蓝等。",
]
}
}
elif 'kaiyuancloud' in domain_url or 'opencomputing' in domain_url or 'localhost' in domain_url: elif 'kaiyuancloud' in domain_url or 'opencomputing' in domain_url or 'localhost' in domain_url:
yezhu['additional_msg'] = { yezhu['additional_msg'] = {
"home": { "home": {

View File

@ -0,0 +1,90 @@
def _parse_page(ns, default_page=1, default_size=20, max_size=200):
try:
current_page = int(ns.get('current_page') or ns.get('page') or default_page)
except (TypeError, ValueError):
current_page = default_page
try:
page_size = int(ns.get('page_size') or default_size)
except (TypeError, ValueError):
page_size = default_size
current_page = max(1, current_page)
page_size = max(1, min(page_size, max_size))
return current_page, page_size, (current_page - 1) * page_size
async def get_user_and_role(ns={}):
"""获取机构下所有用户及用户对应的所有角色"""
orgid = ns.get('orgid')
if not orgid:
return {'status': False, 'msg': '缺少 orgid'}
current_page, page_size, offset = _parse_page(ns)
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
where_sql = """
u.del_flg='0'
AND (
u.orgid=${orgid}$
OR o.parentid=${orgid}$
)
"""
if ns.get('username'):
where_sql += " AND u.username LIKE '%%%%%s%%%%'" % ns.get('username')
if ns.get('mobile'):
where_sql += " AND u.mobile LIKE '%%%%%s%%%%'" % ns.get('mobile')
count_rows = await sor.sqlExe(
"""
SELECT COUNT(*) AS total_count
FROM users u
LEFT JOIN organization o ON u.orgid = o.id AND o.del_flg='0'
WHERE %s
""" % where_sql,
{'orgid': orgid},
)
total_count = count_rows[0]['total_count'] if count_rows else 0
sql = """
SELECT u.*
FROM users u
LEFT JOIN organization o ON u.orgid = o.id AND o.del_flg='0'
LEFT JOIN (
SELECT userid, MAX(create_at) AS userrole_create_at
FROM userrole
WHERE del_flg='0'
GROUP BY userid
) ur_sort ON u.id = ur_sort.userid
WHERE %s
ORDER BY ur_sort.userrole_create_at DESC, u.create_at DESC
LIMIT %d OFFSET %d
""" % (where_sql, page_size, offset)
users = await sor.sqlExe(sql, {'orgid': orgid})
for user in users:
user['roles'] = []
userroles = await sor.R('userrole', {'userid': user['id']})
userroles = sorted(userroles, key=lambda item: item.get('create_at') or '', reverse=True)
for userrole in userroles:
if userrole.get('del_flg') == '1' or not userrole.get('roleid'):
continue
roles = await sor.R('role', {'id': userrole['roleid']})
if len(roles) >= 1 and roles[0].get('del_flg') != '1':
user['roles'].append({
'id': roles[0].get('id'),
'name': roles[0].get('role'),
})
return {
'status': True,
'msg': 'ok',
'data': {
'total_count': total_count,
'current_page': current_page,
'page_size': page_size,
'rows': users,
},
}
except Exception as e:
return {'status': False, 'msg': '获取用户角色失败, %s' % str(e)}
ret = await get_user_and_role(params_kw)
return ret