Browse Source

上传最新的平台代码

main
liwei1dao 2 months ago
parent
commit
b2577e16fa
  1. 53
      apps/admin/app/pages/agents.vue
  2. 27
      apps/admin/app/pages/appgoods.vue
  3. 21
      apps/admin/app/pages/calltranslate.vue
  4. 10
      apps/proto/pay/pay_msg.proto
  5. 42
      apps/proto/user/user_msg_v3.proto
  6. 24
      apps/services/comm/agentcfg.go
  7. 2
      apps/services/comm/log
  8. 0
      apps/services/lego/sys/gin/log-2026-08-20T17-13-40.757.log
  9. 2
      apps/services/lego/sys/log/log.log
  10. 16
      apps/services/modules/console/api_agent.go
  11. 5
      apps/services/modules/console/model_registry.go
  12. 23
      apps/services/modules/console/registry.go
  13. 72
      apps/services/modules/pay/api_getgoodss_v2.go
  14. 15
      apps/services/modules/pay/model.go
  15. 129
      apps/services/modules/user/api_v3_calltranslate.go
  16. 101
      apps/services/modules/user/api_v3_calltranslate_test.go
  17. 50
      apps/services/modules/user/api_v3_getagents.go
  18. 5
      apps/services/modules/user/api_v3_getappconfig.go
  19. 79
      apps/services/modules/user/module_test.go
  20. 275
      apps/services/pb/pay_msg.pb.go
  21. 291
      apps/services/pb/user_msg_v3.pb.go
  22. 120
      apps/services/swagger/docs.go
  23. 120
      apps/services/swagger/swagger.json
  24. 88
      apps/services/swagger/swagger.yaml

53
apps/admin/app/pages/agents.vue

@ -5,7 +5,8 @@
<div>
<h2 class="text-lg font-semibold text-slate-800">Agent 配置</h2>
<p class="text-xs text-slate-400 mt-1">
把已配置的第三方服务编排成可下发客户端的智能体。Agent 按应用隔离:只在下方选中的应用内生效,「全局默认」是所有应用的兜底。
把已配置的第三方服务编排成智能体:对话类下发给客户端当「对话对象」,翻译类供「通话翻译」的语言规则引用。
Agent 按应用隔离:只在下方选中的应用内生效,「全局默认」是所有应用的兜底。
</p>
</div>
<button class="btn btn-primary btn-sm text-white" @click="startAdd">+ 新建 Agent</button>
@ -157,7 +158,7 @@
<!-- Tab 页签 -->
<nav class="ag-tabs">
<button
v-for="t in TABS"
v-for="t in visibleTabs"
:key="t.key"
type="button"
class="ag-tab"
@ -189,6 +190,10 @@
<span class="ttab-desc">{{ t.hint }}</span>
</button>
</div>
<p v-if="isTransType(form.type)" class="ed-tip">
翻译类 Agent 不会出现在客户端的对话列表里,它只是「通话翻译」规则命中后要走的那套服务编排——
建好后去「通话翻译」页加规则,把语言条件指到它。
</p>
<p v-if="isEditing" class="ed-tip">类型创建后不可更改;需要换类型请新建(可用列表卡片上的「复制」快速搬配置)。</p>
</div>
@ -458,7 +463,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { LANG_OPTIONS } from '~/composables/useLangOptions'
@ -534,19 +539,25 @@ const CAT_LLM_VISION = 9 // 多媒体大模型:能力是文本模型的超
const CAT_MCP = 10 // MCP 服务:与其他第三方服务同表,agent 可绑定若干个当工具
// Agent 类型(值与后端 comm.AgentType* 常量严格一致)
// 只保留两种对话型:agent 用来配「对话对象」,翻译走通话翻译规则那套按语言选路,不做成 agent。
// 两类用途、两条链路:
// 对话类(chat / sts-chat) 下发成客户端的「对话对象」列表(user_getagents_v3)。
// 翻译类(translate / ast-translate) 不进对话列表,只作为「通话翻译」规则命中后要走的服务编排
// (通话翻译页的规则按语言选路,选中的就是这里的翻译 Agent)。
interface TypeMeta { value: string; label: string; icon: string; hint: string; color: string; bg: string }
const AGENT_TYPES: TypeMeta[] = [
{ value: 'chat', label: 'chat-agent 对话', icon: '💬', hint: 'STT → LLM → TTS 三段拼接', color: '#b45309', bg: '#fef9c3' },
{ value: 'sts-chat', label: 'sts-chat-agent 端到端对话', icon: '⚡', hint: '单个 STS 服务直接完成语音对话', color: '#be185d', bg: '#fce7f3' },
{ value: 'translate', label: 'translate-agent 三段式翻译', icon: '🧩', hint: 'STT → MT → TTS 三段拼接,供通话翻译规则引用', color: '#059669', bg: '#dcfce7' },
{ value: 'ast-translate', label: 'ast-translate-agent 端到端翻译', icon: '🚀', hint: '单个 AST 服务直接完成语音翻译,供通话翻译规则引用', color: '#c2410c', bg: '#fff7ed' },
]
// 已下线的类型:新建选不到,但库里可能有历史行,列表得能显示出来(否则数据像凭空消失)。
const LEGACY_TYPES: Record<string, { label: string; icon: string; color: string; bg: string }> = {
translate: { label: '翻译(已下线)', icon: '🧩', color: '#64748b', bg: '#f1f5f9' },
'ast-translate': { label: '端到端翻译(已下线)', icon: '🚀', color: '#64748b', bg: '#f1f5f9' },
// 通话翻译规则只能引用这两类(与后端 callTranslateAgentTypes / calltranslate.vue 的 TRANS_TYPES 一致)
const TRANS_TYPE_VALUES = ['translate', 'ast-translate']
function isTransType(t: string): boolean {
return TRANS_TYPE_VALUES.includes(t)
}
const UNKNOWN_TYPE = { label: '未知类型', icon: '🤖', color: '#64748b', bg: '#f1f5f9' }
function typeMeta(t: string): { label: string; icon: string; color: string; bg: string } {
return AGENT_TYPES.find((x) => x.value === t) ?? LEGACY_TYPES[t] ?? { label: t, icon: '🤖', color: '#64748b', bg: '#f1f5f9' }
return AGENT_TYPES.find((x) => x.value === t) ?? { ...UNKNOWN_TYPE, label: t || UNKNOWN_TYPE.label }
}
// 编辑弹窗页签:一屏塞不下这么多配置,按用途分页。
@ -557,6 +568,8 @@ const TABS = [
{ key: 'vars', label: '变量', icon: '🔤' },
{ key: 'mcp', label: 'MCP 服务', icon: '🧩' },
]
// 提示词/变量/MCP 是「对话对象」才有的东西:翻译 Agent 只负责服务编排,页签藏掉,别让运营填了个寂寞。
const CHAT_ONLY_TABS = ['prompt', 'vars', 'mcp']
// 每种类型的服务槽位:key 对应 AgentConfig 字段名,cats 用于筛选可选服务(命中任一即可选)
interface Slot { key: string; slot: string; label: string; cats: number[]; color: string }
@ -637,11 +650,10 @@ const grouped = computed(() => {
type: t.value,
items: filtered.value.filter((a) => a.type === t.value).sort(bySort),
})).filter((g) => g.items.length)
// 库里可能还有已下线类型的历史 agent:单独成组展示,可以看、可以停用/删除,就是不能新建。
for (const [type, meta] of Object.entries(LEGACY_TYPES)) {
const items = filtered.value.filter((a) => a.type === type).sort(bySort)
if (items.length) groups.push({ value: type, type, hint: '已下线的类型,仅供查看与清理', ...meta, items })
}
// 库里可能还有类型对不上的历史行:兜底成一组展示,可以看、可以停用/删除,免得数据像凭空消失。
const known = new Set(AGENT_TYPES.map((t) => t.value))
const rest = filtered.value.filter((a) => !known.has(a.type)).sort(bySort)
if (rest.length) groups.push({ value: '', type: '', hint: '类型无法识别的历史数据,仅供查看与清理', ...UNKNOWN_TYPE, items: rest })
return groups
})
// 卡片上 MCP 数字的悬浮说明:哪些服务、各自收了几个工具
@ -725,8 +737,12 @@ function onScopeChange() {
}
// ===== 音色 =====
// 语音输出用的是哪个服务:对话类是 TTS,端到端对话由 STS 服务自己合成。
const voiceSvcId = computed(() => (form.type === 'sts-chat' ? form.sts_svc_id : form.tts_svc_id))
// 语音输出用的是哪个服务:三段式(chat / translate)是 TTS,端到端由 STS / AST 服务自己合成。
const voiceSvcId = computed(() => {
if (form.type === 'sts-chat') return form.sts_svc_id
if (form.type === 'ast-translate') return form.ast_svc_id
return form.tts_svc_id
})
const voiceProvider = computed(() => svcPool.value.find((s) => s.id === voiceSvcId.value)?.provider ?? '')
// 该服务商是否在内置音色表里:不在就退回手填(火山/OpenAI 等音色表没覆盖,不能把选择框变成死路)
const voiceTableSupported = computed(() => !!voiceProvider.value && ttsVoices.value.some((v) => v.provider === voiceProvider.value))
@ -881,6 +897,11 @@ async function onAvatarChange(e: Event) {
}
}
// ===== 编辑 =====
// 当前类型该显示哪些页签;换类型后若停在已隐藏的页签上,退回基础信息
const visibleTabs = computed(() => TABS.filter((t) => !(isTransType(form.type) && CHAT_ONLY_TABS.includes(t.key))))
watch(() => form.type, () => {
if (!visibleTabs.value.some((t) => t.key === tab.value)) tab.value = 'basic'
})
function tabBadge(key: string): string {
if (key === 'vars') return form.variables.length ? String(form.variables.length) : ''
if (key === 'mcp') return form.mcp_svc_ids.length ? String(form.mcp_svc_ids.length) : ''

27
apps/admin/app/pages/appgoods.vue

@ -30,15 +30,15 @@
<table class="table">
<thead>
<tr>
<th>商品ID</th><th>名称</th><th>类型</th><th>默认价格</th><th>发放内容</th><th>多语言</th><th>状态</th><th class="text-right">操作</th>
<th>商品ID</th><th>名称</th><th>类型</th><th>默认价格</th><th>发放内容</th><th>状态</th><th class="text-right">操作</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="8" class="text-center py-8"><span class="loading loading-spinner loading-sm"></span></td>
<td colspan="7" class="text-center py-8"><span class="loading loading-spinner loading-sm"></span></td>
</tr>
<tr v-else-if="!filteredRows.length">
<td colspan="8" class="text-center text-slate-400 py-8">暂无商品</td>
<td colspan="7" class="text-center text-slate-400 py-8">暂无商品</td>
</tr>
<tr v-for="g in filteredRows" :key="g.id" class="hover">
<td class="font-mono text-sm font-medium">{{ g.goods_id }}</td>
@ -46,12 +46,6 @@
<td class="text-sm text-slate-500">{{ usageLabel(g.usagetype) }}</td>
<td class="text-sm">{{ g.price }}</td>
<td class="text-xs text-slate-500">{{ rewardText(g) || '-' }}</td>
<td>
<div v-if="localeCodes(g).length" class="flex flex-wrap gap-1">
<span v-for="code in localeCodes(g)" :key="code" class="badge badge-ghost badge-sm font-mono">{{ code }}</span>
</div>
<span v-else class="text-xs text-slate-400">未配置</span>
</td>
<td>
<span class="badge badge-sm" :class="g.enable ? 'badge-success text-white' : 'badge-ghost'">{{ g.enable ? '上架' : '下架' }}</span>
</td>
@ -263,17 +257,20 @@ function usageLabel(v: number) {
return USAGE_TYPES.find((o) => o.value === v)?.label ?? String(v)
}
function localeCodes(g: Goods) {
return Object.keys(g.locales || {}).sort()
// 秒→分钟(会议/翻译资源点入库单位是秒,见 pay 发货的 Addmeetsecond/Addtradesecond)。
// 不足 1 分钟或除不尽时保留一位小数,避免 90 秒显示成 1 分钟。
function minutesText(sec: number) {
const m = sec / 60
return Number.isInteger(m) ? `${m} 分钟` : `${m.toFixed(1)} 分钟`
}
// 发放内容摘要(列表列,只显示非 0 项)
function rewardText(g: Goods) {
const parts: string[] = []
if (g.viptime) parts.push(`VIP ${g.viptime}`)
if (g.ainum) parts.push(`AI ${g.ainum}`)
if (g.meetnum) parts.push(`会议 ${g.meetnum}`)
if (g.tradenum) parts.push(`翻译 ${g.tradenum}`)
if (g.viptime) parts.push(`VIP ${g.viptime} 天`)
if (g.ainum) parts.push(`AI ${g.ainum} 点`)
if (g.meetnum) parts.push(`会议 ${minutesText(g.meetnum)}`)
if (g.tradenum) parts.push(`翻译 ${minutesText(g.tradenum)}`)
return parts.join(' · ')
}

21
apps/admin/app/pages/calltranslate.vue

@ -98,7 +98,9 @@
<select v-model="ed.agentId" class="select select-bordered select-sm w-full">
<option value="" disabled>{{ transAgents.length ? '请选择 Agent' : '(无可用 Agent,请先去 Agent 配置里创建)' }}</option>
<optgroup v-for="g in agentGroups" :key="g.type" :label="g.label">
<option v-for="a in g.items" :key="a.id" :value="a.id">{{ a.name || a.id }}</option>
<option v-for="a in g.items" :key="a.id" :value="a.id">
{{ a.name || a.id }}{{ a.global ? '(全局兜底)' : '' }}{{ a.enable ? '' : '(已停用)' }}
</option>
</optgroup>
</select>
</div>
@ -259,6 +261,7 @@ interface Agent {
description: string
enable: boolean
support_langs: string
global?: boolean // 来自「全局默认」作用域的兜底 agent(当前应用没配同 id 时可引用)
}
interface Rule {
id: number
@ -382,13 +385,17 @@ async function loadScopeApps() {
scopeApps.value = []
}
}
// 选了应用时要连全局默认的 agent 一起拉:规则可以引用全局兜底的翻译 Agent
// (后端 findAgentForRule 就是「应用作用域优先、未命中回退全局」),同 id 以本应用的为准。
async function loadAgents() {
try {
const d = await webApi<{ items: Agent[] }>('getagents', { app_name: scopeApp.value })
agents.value = d?.items ?? []
} catch {
agents.value = []
}
const app = scopeApp.value
const [own, global] = await Promise.all([
webApi<{ items: Agent[] }>('getagents', { app_name: app }).catch(() => null),
app ? webApi<{ items: Agent[] }>('getagents', { app_name: '' }).catch(() => null) : Promise.resolve(null),
])
const list = own?.items ?? []
const ids = new Set(list.map((a) => a.id))
agents.value = [...list, ...(global?.items ?? []).filter((a) => !ids.has(a.id)).map((a) => ({ ...a, global: true }))]
}
async function loadRules() {
try {

10
apps/proto/pay/pay_msg.proto

@ -12,6 +12,16 @@ message PayGetGoodssResp{
repeated DBGoods goodss = 1;
}
//读取商品列表 v2:改读**管理后台的应用商品配置**(console 公共库 app_goods,按应用名隔离),
//而不是各业务库自己维护的 goods 表;口径与支付通知 v2(pay_paypalnotifyv2)发货一致。
message PayGetGoodssV2Req{
}
//响应沿用 DBGoods 结构,客户端解析与 v1 完全一致(id = 后台配置的商品标识 goods_id)。
message PayGetGoodssV2Resp{
repeated DBGoods goodss = 1;
}
//读取用户订单信息
message PayGetOrderListReq{
int32 page = 1; //@go_tags(`json:"page"`) 页码(从1开始)

42
apps/proto/user/user_msg_v3.proto

@ -11,6 +11,8 @@ import "db.proto";
// - agents 换成 AgentItemV3(与 user_getagents_v3 同一结构):带 type(chat|sts-chat) 与服务编排
// (stt/llm/tts/sts_svc_id)、MCP 绑定、变量、已补全的音色表。v2 下发的旧 DBAgent 结构没有这些,
// 客户端拿到 agent 却不知道该走哪条链路、调哪个服务。
// - 新增 call_translate:通话翻译的选路规则 + 规则用到的翻译 agent。后台把「什么语言走哪套
// 服务编排」配成规则,这里按用户所在区域筛好一并下发,客户端据此在通话里选路。
// 整体响应经网关 AES-CBC 加密(响应头 X-Encrypted:1),故 fields 里的凭据不再单独加密。
message UserGetAppConfigV3Req{
@ -39,12 +41,46 @@ message UserGetAppConfigV3Resp{
repeated DBProduct products = 3; //@go_tags(`json:"products"`) 产品列表
repeated ThirdSvcItem thirdsvcs = 4; //@go_tags(`json:"thirdsvcs"`) 第三方服务(含 MCP 类,按用户区域合并、凭据已解密为明文)
MeetCapabilitiesItem meet = 5; //@go_tags(`json:"meet"`) 会议记录能力(语言 + 总结大模型)
CallTranslateItem call_translate = 6; //@go_tags(`json:"call_translate"`) 通话翻译配置(本区域生效的选路规则 + 规则引用的翻译 agent)
}
// ===================== 通话翻译(call_translate) =====================
//
// 后台「通话翻译配置」把「什么语言走哪套服务编排」拆成两层:规则负责按语言选路,
// 选中的翻译 agent 负责给出具体服务(三段式 translate = stt+mt+tts;端到端 ast-translate = ast)。
// 这里下发的是**已按用户所在区域筛过**的那一份:不限区域(region=0)的兜底规则与本区域的规则,
// 已停用的规则、以及引用不到可用翻译 agent 的规则都不会出现。
//通话翻译选路规则:一条规则覆盖「多源语言 × 多目标语言」,命中后走 agent_id 指定的翻译 agent。
message CallTranslateRuleItem{
uint64 id = 1; //@go_tags(`json:"id"`) 规则 id(仅用于日志/排障)
string name = 2; //@go_tags(`json:"name"`) 规则名(仅便于识别)
string src_langs = 3; //@go_tags(`json:"src_langs"`) 源语言集合,逗号分隔 BCP-47;单个 "*" = 任意语言
string dst_langs = 4; //@go_tags(`json:"dst_langs"`) 目标语言集合,同上
bool bidir = 5; //@go_tags(`json:"bidir"`) true=反向(dst→src)也命中这条规则
string agent_id = 6; //@go_tags(`json:"agent_id"`) 命中后使用的翻译 agent(在同响应的 call_translate.agents 里)
int32 priority = 7; //@go_tags(`json:"priority"`) 优先级,越大越先匹配
}
//通话翻译配置:规则集 + 规则引用到的翻译 agent。
//
//匹配口径(与服务端 comm.MatchCallTranslateRule 完全一致,客户端照此实现即可):
// 1. 两侧都命中才算候选。单侧命中分三档:全等=2、主语言子标签相同(zh-CN 规则接住 zh/zh-TW)=1、被 "*" 接住=0;
// 规则的语言集合里取最好的一档。bidir 规则同时试反向,取更好的一次。
// 2. 候选间取最优:优先级大 > 两侧得分之和高 > 精确度高(src/dst 每一侧非 "*" 各记 1 分) > id 小。
// 3. rules 已按「优先级降序 → id 升序」排好,但**不能只取第一条命中的**——得分与精确度会改变名次。
// 4. 一条都不命中 = 该方向没配通话翻译。
message CallTranslateItem{
repeated CallTranslateRuleItem rules = 1; //@go_tags(`json:"rules"`) 本区域生效的规则(已按优先级降序 → id 升序排好)
repeated AgentItemV3 agents = 2; //@go_tags(`json:"agents"`) 上面规则引用到的翻译 agent(type=translate|ast-translate),结构同 agents
}
// v3 agent 列表:相比 v2 多了「绑定的 MCP 服务 + 变量」,并且 voice_map 是**已补全**的——
// 服务端按内置音色表(comm/ttsvoice.go)把 agent 支持的每种语言都解析出最终音色,
// 客户端不必自带音色表,切语言时按 language 直接取即可。
// 只下发 chat / sts-chat 两类(翻译类已下线),故服务槽位也只保留这两类用得到的。
// 顶层 agents 只下发对话类(chat / sts-chat);翻译类 agent(translate / ast-translate) 复用同一结构,
// 但走另一条链路——它是通话翻译规则的服务编排载体,在 call_translate.agents 里下发。
// 故四类的服务槽位都在这个结构里,按 type 取用其中若干,用不到的为空串。
message UserGetAgentsV3Req{
}
@ -67,7 +103,7 @@ message AgentMcpBindingItem{
message AgentItemV3{
string id = 1; //@go_tags(`json:"id"`) agent id
string name = 2; //@go_tags(`json:"name"`) 名称
string type = 3; //@go_tags(`json:"type"`) chat|sts-chat
string type = 3; //@go_tags(`json:"type"`) chat|sts-chat(call_translate.agents 里则为 translate|ast-translate)
string description = 4; //@go_tags(`json:"description"`) 描述
string avatar_url = 5; //@go_tags(`json:"avatar_url"`) 头像
string stt_svc_id = 6; //@go_tags(`json:"stt_svc_id"`) 识别服务(chat)
@ -85,6 +121,8 @@ message AgentItemV3{
repeated AgentVariableItem variables = 18; //@go_tags(`json:"variables"`) 变量定义
int32 sort = 19; //@go_tags(`json:"sort"`) 排序
repeated AgentMcpBindingItem mcps = 20; //@go_tags(`json:"mcps"`) MCP 绑定(含每个服务允许的工具);mcp_svc_ids 是它的 id 平铺,保留给老客户端
string mt_svc_id = 21; //@go_tags(`json:"mt_svc_id"`) 机器翻译服务(translate 三段式翻译;对话类为空)
string ast_svc_id = 22; //@go_tags(`json:"ast_svc_id"`) 端到端语音翻译服务(ast-translate;对话类为空)
}
message UserGetAgentsV3Resp{

24
apps/services/comm/agentcfg.go

@ -10,25 +10,39 @@ package comm
// 类型与其引用的服务(均引用 ThirdSvcConfig.Id):
// chat 对话: SttSvcId + LlmSvcId + TtsSvcId
// sts-chat 端到端对话: StsSvcId
// translate 三段式翻译: SttSvcId + MtSvcId + TtsSvcId
// ast-translate 端到端翻译: AstSvcId
//
// 翻译类(translate / ast-translate)已下线:agent 只用来配「对话对象」,
// 翻译能力由通话翻译规则(CallTranslateRule)那套按语言选路,不再做成 agent。
// 两个常量保留,只为读得懂库里可能存在的历史行——新建/改存不再接受它们。
// 两类用途、两条下发链路,别混:
// 对话类(chat / sts-chat) = 「对话对象」,由 user_getagents_v3 下发成客户端的 agent 列表。
// 翻译类(translate / ast-translate) = 通话翻译的服务编排载体,本身不进 agent 列表,
// 只被通话翻译规则(CallTranslateRule.AgentId)按语言选路引用。
// 翻译类一度下线过(翻译只留规则、不留 agent),但规则里「命中后走哪套 asr/mt/tts」仍得有地方配,
// 于是恢复:规则负责选路,翻译 agent 负责编排。
// Agent 类型取值(与前端一致)。
const (
AgentTypeChat = "chat"
AgentTypeStsChat = "sts-chat"
// 下面两个已下线,仅用于识别历史数据,勿用于新建。
AgentTypeTranslate = "translate"
AgentTypeAstTranslate = "ast-translate"
)
// AgentTypeSelectable 当前允许新建/保存的 agent 类型。
// AgentTypeSelectable 允许新建/保存的 agent 类型。
func AgentTypeSelectable(t string) bool {
return AgentTypeIsChat(t) || AgentTypeIsTranslate(t)
}
// AgentTypeIsChat 是否「对话对象」类:只有这类才下发进客户端的 agent 列表。
func AgentTypeIsChat(t string) bool {
return t == AgentTypeChat || t == AgentTypeStsChat
}
// AgentTypeIsTranslate 是否翻译类:只被通话翻译规则引用,不进 agent 列表。
func AgentTypeIsTranslate(t string) bool {
return t == AgentTypeTranslate || t == AgentTypeAstTranslate
}
// AgentVariable agent 的变量项:客户端在拼 system_prompt / 开场白时做占位替换,
// 形如 {{key}}。运营在后台维护「有哪些变量、默认值是什么」,具体取值由客户端按
// 运行时上下文(用户昵称、设备名等)填,没填就用 DefValue。

2
apps/services/comm/log

@ -1 +1 @@
2026/08/14 16:46:43.095 ERROR comm/moduleconfig.go:213 [ModuleConfig] 密钥字段解密失败,已跳过该字段(保留默认值) module=wechatpay key=PrivateKeyPath err=GCM 解密失败: cipher: message authentication failed 本服务key指纹=sha256:19731e34cd8c [不足 32 字节,已右侧补零(原长 30)] 【诊断】密文是用「空 key(FIELD_ENCRYPT_KEY 未注入时补零出的全零密钥)」(指纹 sha256:17a63bfb3b5b [!!未配置 FIELD_ENCRYPT_KEY,正在使用全零密钥])加密的存量数据,与本服务当前 key 不是同一把 —— 到 console 后台把该字段重新填一遍保存,即可用当前 key 重新加密落库
2026/08/20 17:15:59.311 ERROR comm/moduleconfig.go:247 [ModuleConfig] 密钥字段解密失败,已跳过该字段(保留默认值) module=wechatpay key=PrivateKeyPath err=GCM 解密失败: cipher: message authentication failed 本服务key指纹=sha256:19731e34cd8c [不足 32 字节,已右侧补零(原长 30)] 【诊断】密文是用「空 key(FIELD_ENCRYPT_KEY 未注入时补零出的全零密钥)」(指纹 sha256:17a63bfb3b5b [!!未配置 FIELD_ENCRYPT_KEY,正在使用全零密钥])加密的存量数据,与本服务当前 key 不是同一把 —— 到 console 后台把该字段重新填一遍保存,即可用当前 key 重新加密落库

0
apps/services/lego/sys/gin/log-2026-08-20T17-13-40.757.log

2
apps/services/lego/sys/log/log.log

@ -1 +1 @@
2026/02/05 18:03:46.776 ERROR log/sys_test.go:36 妈妈咪呀!
2026/08/20 17:13:42.666 ERROR log/sys_test.go:36 妈妈咪呀!

16
apps/services/modules/console/api_agent.go

@ -97,16 +97,14 @@ func validateAgentSvcRefs(a *AgentConfig) string {
return ""
}
// validateAgentType 校验类型可用。翻译类已下线,只在「历史行本来就是这个类型且没改」时放行,
// 保证老数据还能打开、改名字、停用,而不是一保存就被卡住。
func validateAgentType(a *AgentConfig, old *AgentConfig) string {
// validateAgentType 校验类型可用。四类都可建:对话两类下发成客户端 agent 列表,
// 翻译两类供通话翻译规则按语言选路引用。
func validateAgentType(a *AgentConfig) string {
if comm.AgentTypeSelectable(a.Type) {
return ""
}
if old != nil && old.Type == a.Type {
return "" // 历史行保持原类型,放行
}
return "智能体类型不支持: " + a.Type + "(现只支持 对话(chat) / 端到端对话(sts-chat))"
return "智能体类型不支持: " + a.Type +
"(支持 对话(chat) / 端到端对话(sts-chat) / 三段式翻译(translate) / 端到端翻译(ast-translate))"
}
// validateAgentMcpRefs 校验绑定的 MCP 服务:存在且类别含 MCP。空列表直接通过。
@ -254,7 +252,7 @@ func (this *serverComp) addAgent(c *gin.Context) {
writeErr(c, pb.ErrorCode_ReqParameterError, "智能体名称 必填")
return
}
if msg := validateAgentType(&m, nil); msg != "" {
if msg := validateAgentType(&m); msg != "" {
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
return
}
@ -313,7 +311,7 @@ func (this *serverComp) updateAgent(c *gin.Context) {
writeErr(c, pb.ErrorCode_DBError, "智能体不存在: "+m.Id)
return
}
if msg := validateAgentType(&m, old); msg != "" {
if msg := validateAgentType(&m); msg != "" {
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
return
}

5
apps/services/modules/console/model_registry.go

@ -86,6 +86,11 @@ func (this *modelComp) Init(service core.IService, module core.IModule, comp cor
if err = ensureConfigTables(); err != nil {
this.module.Errorln(err)
}
// 月结算单表建在 console 主库。同 ensureConfigTables 的理由(新表、CreateTable 对已存在表是
// 幂等空操作),即便 SkipEnsureTables=true 也要执行——否则结算页查询直接 42P01。
if err = ensureSettlementTable(); err != nil {
this.module.Errorln(err)
}
return
}

23
apps/services/modules/console/registry.go

@ -163,19 +163,30 @@ func ensureDeviceTables() error {
return err
}
_ = adb.AutoIncrementStart(comm.TableBrand, "id", 0xA001)
// createtime 是后加字段(品牌商建档时间):CreateTable 对已存在的表跳过 AutoMigrate,故显式补列。
// 历史品牌商为 0,后台列表按此展示为空。
if res := adb.Exec("ALTER TABLE " + comm.TableBrand + " ADD COLUMN IF NOT EXISTS createtime bigint DEFAULT 0"); res.Error != nil {
// createtime/sharerate 是后加字段(品牌商建档时间、分成比例万分比):CreateTable 对已存在的表
// 跳过 AutoMigrate,故显式补列。历史品牌商 createtime=0(列表展示为空)、sharerate=0(不分成)。
for _, ddl := range []string{
"ALTER TABLE " + comm.TableBrand + " ADD COLUMN IF NOT EXISTS createtime bigint DEFAULT 0",
"ALTER TABLE " + comm.TableBrand + " ADD COLUMN IF NOT EXISTS sharerate integer DEFAULT 0",
} {
if res := adb.Exec(ddl); res.Error != nil {
return res.Error
}
}
if err := adb.CreateTable(comm.TableChannel, &pb.DBChannel{}); err != nil {
return err
}
// official 是后加字段(品牌官方渠道标记):CreateTable 对已存在的表跳过 AutoMigrate,故显式补列。
// 历史行一律 false——存量品牌商没有官方渠道,需要的话在后台手工建一条。
if res := adb.Exec("ALTER TABLE " + comm.TableChannel + " ADD COLUMN IF NOT EXISTS official boolean DEFAULT false"); res.Error != nil {
// official/sharerate 是后加字段(品牌官方渠道标记、渠道分成比例):CreateTable 对已存在的表
// 跳过 AutoMigrate,故显式补列。历史行 official=false(存量品牌商没有官方渠道,需要的话在
// 后台手工建一条)、sharerate=0(不分成)。
for _, ddl := range []string{
"ALTER TABLE " + comm.TableChannel + " ADD COLUMN IF NOT EXISTS official boolean DEFAULT false",
"ALTER TABLE " + comm.TableChannel + " ADD COLUMN IF NOT EXISTS sharerate integer DEFAULT 0",
} {
if res := adb.Exec(ddl); res.Error != nil {
return res.Error
}
}
if err := adb.CreateTable(comm.TableProductionBatch, &pb.DBProductionBatch{}); err != nil {
return err
}

72
apps/services/modules/pay/api_getgoodss_v2.go

@ -0,0 +1,72 @@
package pay
import (
"yunyan/comm"
"yunyan/lego/sys/log"
"yunyan/pb"
)
// @Summary 获取商品列表 v2
// @Description 获取商品列表(读管理后台「应用商品配置」,按应用隔离)
// @Tags Pay
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param order body pb.PayGetGoodssV2Req true "获取商品列表 v2"
// @Success 200 {object} comm.HttpResult{data=pb.PayGetGoodssV2Resp} "商品列表"
// @Router /api/home/pay_getgoodssv2 [post]
//
// GetGoodssV2 与 v1(GetGoodss)返回结构完全一致,差别只有商品来源:
// v1 读**各业务库自己的 goods 表**,v2 读**管理后台的应用商品配置**
// (console 公共库 app_goods,按 comm.AppName() 隔离),与支付通知 v2 的发货口径同源——
// 后台「应用商品配置」页改完即生效,多区域部署共用一份,不必再逐个业务库同步。
//
// 只下发已上架(Enable)的商品,顺序按后台配置的 sort;后台一个商品都没配时返回空列表,
// 不报错——让客户端表现为「暂无商品」,而不是弹错误。
func (this *apiComp) GetGoodssV2(session comm.IUserSession, req *pb.PayGetGoodssV2Req) (resp *pb.PayGetGoodssV2Resp, errdata *pb.ErrorData) {
var (
allgoodss []*comm.AppGoods
goodss []*pb.DBGoods
err error
)
if allgoodss, err = this.module.model.appGoodss(); err != nil {
errdata = &pb.ErrorData{
Code: pb.ErrorCode_DBError,
Message: err.Error(),
}
this.module.Error("GetGoodssV2 失败!",
log.Field{Key: "app", Value: comm.AppName()},
log.Field{Key: "uid", Value: session.GetUserId()},
log.Field{Key: "err", Value: err.Error()})
return
}
goodss = make([]*pb.DBGoods, 0, len(allgoodss))
for _, v := range allgoodss {
if !v.Enable {
continue
}
goodss = append(goodss, dbGoodsOfAppGoods(v))
}
resp = &pb.PayGetGoodssV2Resp{
Goodss: goodss,
}
return
}
// dbGoodsOfAppGoods 把后台商品配置转成客户端认识的 DBGoods。
// id 取 GoodsId(下单时写进订单 product_id 的那个标识,不是后台自增主键 Id),
// localname 原样透传(同一份 JSON 形态,多语言由客户端按 lang 取)。
func dbGoodsOfAppGoods(g *comm.AppGoods) *pb.DBGoods {
return &pb.DBGoods{
Id: g.GoodsId,
Enable: g.Enable,
Name: g.Name,
Localname: g.Localname,
Price: g.Price,
Usagetype: pb.UsageType(g.Usagetype),
Ainum: g.Ainum,
Meetnum: g.Meetnum,
Tradenum: g.Tradenum,
Viptime: g.Viptime,
}
}

15
apps/services/modules/pay/model.go

@ -87,6 +87,21 @@ func (this *modelComp) appGoods(goodsId string) (model *comm.AppGoods, err error
return model, nil
}
// appGoodss 列出**管理后台配置**的本应用全部商品(console 公共库 app_goods),
// 排序与后台「应用商品配置」页一致(sort 升序、其次 id),上下架状态由调用方决定要不要过滤。
// 应用名(comm.AppName())没配时返回空列表——没有「全局商品」这一档,见 console/model_goods.go。
func (this *modelComp) appGoodss() (models []*comm.AppGoods, err error) {
models = make([]*comm.AppGoods, 0)
appName := comm.AppName()
if appName == "" {
return
}
if err = postgres.Table(comm.TableAppGoods).Where("app_name=?", appName).Order("sort ASC, id ASC").Find(&models).Error; err != nil {
this.module.Errorln(err)
}
return
}
// getProduct 取产品(订单快照需要产品所属品牌商)。
func (this *modelComp) getProduct(id uint32) (model *pb.DBProduct, err error) {
model = &pb.DBProduct{}

129
apps/services/modules/user/api_v3_calltranslate.go

@ -0,0 +1,129 @@
package user
import (
"sort"
"yunyan/comm"
"yunyan/lego/sys/log"
"yunyan/lego/sys/postgres"
"yunyan/pb"
)
// 通话翻译配置的下发(user_getappconfig_v3 的 call_translate 字段)。
//
// 对应后台「通话翻译配置」的两层模型:规则(call_translate_rule)只管「什么语言走哪个 agent」,
// 具体调哪套 asr/mt/tts(或端到端 ast)在翻译 agent(agent_config, type=translate|ast-translate) 里。
// 两层一起下发,客户端按 comm.MatchCallTranslateRule 的口径选路(口径同步写在 proto 注释里)。
//
// 之所以下发规则集而不是让客户端每次通话来问一次:通话建立时选路要即时,多一次往返就是首句延迟。
// callTranslateV3 组装下发客户端的通话翻译配置:本区域生效的规则 + 这些规则引用到的翻译 agent。
//
// 三处筛选都在服务端做掉,客户端不必再判:
// - 区域:只留本区域的规则与不限区域(RegionAny)的兜底规则;
// - 启停:停用的规则、停用的 agent 都不下发;
// - 悬空引用:agent 找不到或类型不对的规则整条丢掉——下发一条指向不存在 agent 的规则,
// 客户端要到通话中途才发现没法翻译。
//
// 取不到配置不让整个 appconfig 失败:通话翻译是可选能力,没配它的应用(乃至库里还没建这张表的
// 老部署)应当照常拿到其余配置,故这里只记日志、返回空壳(字段都在、列表为空)。
func (this *apiV3Comp) callTranslateV3(region pb.Region) *pb.CallTranslateItem {
out := &pb.CallTranslateItem{
Rules: []*pb.CallTranslateRuleItem{},
Agents: []*pb.AgentItemV3{},
}
app := comm.AppName()
rules, err := loadCallTranslateRules(app, region)
if err != nil {
this.module.Error("加载通话翻译规则失败!",
log.Field{Key: "app", Value: app}, log.Field{Key: "err", Value: err.Error()})
return out
}
if len(rules) == 0 {
return out
}
list, err := pickAgentsV3(app, comm.AgentTypeIsTranslate)
if err != nil {
this.module.Error("加载翻译 agent 失败!",
log.Field{Key: "app", Value: app}, log.Field{Key: "err", Value: err.Error()})
return out
}
byId := make(map[string]*comm.AgentConfig, len(list))
for _, a := range list {
byId[a.Id] = a
}
providerOf := this.svcProviders(app)
added := make(map[string]bool, len(byId))
for _, r := range rules {
a, ok := byId[r.AgentId]
if !ok {
this.module.Warn("通话翻译规则引用不到可用的翻译 agent,已跳过",
log.Field{Key: "rule", Value: r.Id}, log.Field{Key: "agent", Value: r.AgentId})
continue
}
out.Rules = append(out.Rules, &pb.CallTranslateRuleItem{
Id: r.Id,
Name: r.Name,
SrcLangs: r.SrcLangs,
DstLangs: r.DstLangs,
Bidir: r.Bidir,
AgentId: r.AgentId,
Priority: r.Priority,
})
if !added[a.Id] {
added[a.Id] = true
out.Agents = append(out.Agents, agentItemV3Of(a, providerOf[voiceSvcIdOf(a)]))
}
}
return out
}
// loadCallTranslateRules 取本应用作用域下、本区域生效且启用的规则,按生效顺序(优先级降序 → id 升序)排好。
//
// 作用域是「整套覆盖」而非按条合并:应用自己配了规则就只用应用那套,一条都没有才回退全局默认。
// 规则之间靠优先级互相压制,两套混在一起优先级会打架——全局的兜底规则可能压过应用的精确规则。
// (规则引用的 agent 仍是按条解析、应用优先回退全局,见 pickAgentsV3,与 console 的 findAgentForRule 一致。)
func loadCallTranslateRules(app string, region pb.Region) ([]*comm.CallTranslateRule, error) {
rows := make([]*comm.CallTranslateRule, 0)
if err := postgres.Find(comm.TableCallTranslateRule, &rows, "(app_name=? OR app_name='')", app); err != nil && err != postgres.ErrNoDocuments {
return nil, err
}
return selectCallTranslateRules(rows, app, region), nil
}
// selectCallTranslateRules 从「应用行 + 全局行」的原始规则里挑出实际下发的那批(作用域覆盖 → 区域/启停过滤 → 排序)。
// 与查库分开,便于直接用数据验证选取口径。
func selectCallTranslateRules(rows []*comm.CallTranslateRule, app string, region pb.Region) []*comm.CallTranslateRule {
scoped := make([]*comm.CallTranslateRule, 0, len(rows))
if app != "" {
for _, r := range rows {
if r.AppName == app {
scoped = append(scoped, r)
}
}
}
if len(scoped) == 0 {
for _, r := range rows {
if r.AppName == "" {
scoped = append(scoped, r)
}
}
}
out := make([]*comm.CallTranslateRule, 0, len(scoped))
for _, r := range scoped {
if r.Enable && (r.Region == comm.RegionAny || r.Region == int32(region)) {
out = append(out, r)
}
}
sort.Slice(out, func(i, j int) bool {
if out[i].Priority != out[j].Priority {
return out[i].Priority > out[j].Priority
}
return out[i].Id < out[j].Id
})
return out
}

101
apps/services/modules/user/api_v3_calltranslate_test.go

@ -0,0 +1,101 @@
package user
import (
"encoding/json"
"testing"
"yunyan/comm"
"yunyan/pb"
)
// 规则选取口径:应用自己配了就整套用应用的(不与全局混),只留本区域+不限区域的、启用的,
// 并按「优先级降序 → id 升序」排好。混合两套作用域会让全局兜底规则压过应用的精确规则。
func TestV3CallTranslateRuleSelection(t *testing.T) {
rows := []*comm.CallTranslateRule{
{Id: 1, AppName: "", Region: comm.RegionAny, Priority: 900, Enable: true}, // 全局:应用有规则时整套不用
{Id: 2, AppName: "ym", Region: int32(pb.Region_RegionChina), Priority: 100, Enable: true}, // 本区域
{Id: 3, AppName: "ym", Region: comm.RegionAny, Priority: 100, Enable: true}, // 不限区域,同优先级 → 排 id 2 之后
{Id: 4, AppName: "ym", Region: int32(pb.Region_RegionUSA), Priority: 500, Enable: true}, // 别的区域
{Id: 5, AppName: "ym", Region: int32(pb.Region_RegionChina), Priority: 999, Enable: false}, // 已停用
{Id: 6, AppName: "ym", Region: int32(pb.Region_RegionChina), Priority: 200, Enable: true}, // 优先级最高的有效规则
}
got := selectCallTranslateRules(rows, "ym", pb.Region_RegionChina)
want := []uint64{6, 2, 3}
if len(got) != len(want) {
t.Fatalf("选中规则数不对:want %v,got %d 条", want, len(got))
}
for i, id := range want {
if got[i].Id != id {
t.Fatalf("规则顺序不对:want %v,got[%d]=%d", want, i, got[i].Id)
}
}
}
// 应用一条规则都没配时回退全局默认那套,否则新接入的应用通话翻译直接不可用。
func TestV3CallTranslateFallsBackToGlobal(t *testing.T) {
rows := []*comm.CallTranslateRule{
{Id: 1, AppName: "", Region: comm.RegionAny, Priority: 10, Enable: true},
{Id: 2, AppName: "other", Region: comm.RegionAny, Priority: 99, Enable: true},
}
got := selectCallTranslateRules(rows, "ym", pb.Region_RegionChina)
if len(got) != 1 || got[0].Id != 1 {
t.Fatalf("应回退到全局规则 #1,got %v", got)
}
}
// 全局作用域(app_name=”)自己请求时不该把别的应用的规则也算进来。
func TestV3CallTranslateGlobalScopeExcludesAppRows(t *testing.T) {
rows := []*comm.CallTranslateRule{
{Id: 1, AppName: "", Region: comm.RegionAny, Priority: 10, Enable: true},
{Id: 2, AppName: "ym", Region: comm.RegionAny, Priority: 99, Enable: true},
}
got := selectCallTranslateRules(rows, "", pb.Region_RegionChina)
if len(got) != 1 || got[0].Id != 1 {
t.Fatalf("全局作用域只该拿到全局规则 #1,got %v", got)
}
}
// appconfig 的 call_translate 得是「拿了就能选路」的一份:规则字段齐、agent 带着服务编排。
// 客户端只调 appconfig 就要完成通话翻译选路,缺一层就得多一次往返。
func TestV3AppConfigCallTranslateContract(t *testing.T) {
resp := &pb.UserGetAppConfigV3Resp{CallTranslate: &pb.CallTranslateItem{
Rules: []*pb.CallTranslateRuleItem{{
Id: 7, Name: "中日互译", SrcLangs: "zh-CN", DstLangs: "ja-JP",
Bidir: true, AgentId: "trans-abc", Priority: 100,
}},
Agents: []*pb.AgentItemV3{{
Id: "trans-abc", Name: "三段式翻译", Type: comm.AgentTypeTranslate,
SttSvcId: "svc_stt", MtSvcId: "svc_mt", TtsSvcId: "svc_tts",
}},
}}
buf, err := json.Marshal(resp)
if err != nil {
t.Fatal(err)
}
var got struct {
CallTranslate struct {
Rules []map[string]any `json:"rules"`
Agents []map[string]any `json:"agents"`
} `json:"call_translate"`
}
if err = json.Unmarshal(buf, &got); err != nil {
t.Fatal(err)
}
if len(got.CallTranslate.Rules) != 1 || len(got.CallTranslate.Agents) != 1 {
t.Fatalf("call_translate 解析失败:%s", buf)
}
for _, k := range []string{"id", "name", "src_langs", "dst_langs", "bidir", "agent_id", "priority"} {
if _, ok := got.CallTranslate.Rules[0][k]; !ok {
t.Fatalf("规则缺字段 %s:%s", k, buf)
}
}
// 翻译 agent 的服务槽位:三段式看 stt/mt/tts,端到端看 ast——少一个客户端就不知道调哪个服务。
for _, k := range []string{"type", "stt_svc_id", "mt_svc_id", "tts_svc_id", "ast_svc_id", "voice_map"} {
if _, ok := got.CallTranslate.Agents[0][k]; !ok {
t.Fatalf("翻译 agent 缺字段 %s:%s", k, buf)
}
}
if got.CallTranslate.Agents[0]["type"] != comm.AgentTypeTranslate {
t.Fatalf("翻译 agent 的 type 没下发对:%s", buf)
}
}

50
apps/services/modules/user/api_v3_getagents.go

@ -11,7 +11,7 @@ import (
// @Summary 获取 agent 列表(v3)
// @Description 下发后台「Agent 配置」,相比 v2 增加:绑定的 MCP 服务 id、变量定义,且 voice_map 已按内置音色表补全
// (agent 支持的每种语言都给出最终音色,客户端切语言时直接取,不用自带音色表)。只下发 chat / sts-chat 两类。
// (agent 支持的每种语言都给出最终音色,客户端切语言时直接取,不用自带音色表)。只下发对话类(chat / sts-chat)。
// @Tags User
// @Accept json
// @Produce json
@ -37,17 +37,31 @@ func (this *apiV3Comp) GetAgents(session comm.IUserSession, req *pb.UserGetAgent
// 与服务编排(历史上 appconfig 下发的是已废弃的旧 agent 表,客户端不知道该走 chat 还是 sts-chat)。
func (this *apiV3Comp) agentItemsV3() ([]*pb.AgentItemV3, error) {
app := comm.AppName()
// 只下发「对话对象」:翻译类 agent 是通话翻译规则的编排载体,走 call_translate 那条链路。
list, err := pickAgentsV3(app, comm.AgentTypeIsChat)
if err != nil {
return nil, err
}
providerOf := this.svcProviders(app)
items := make([]*pb.AgentItemV3, 0, len(list))
for _, a := range list {
items = append(items, agentItemV3Of(a, providerOf[voiceSvcIdOf(a)]))
}
return items, nil
}
// pickAgentsV3 取本应用作用域下可下发的 agent 配置,accept 决定收哪几类(对话类 / 翻译类)。
// 同 id 应用行覆盖全局行;仅启用的(应用行停用则回退全局行,与第三方服务下发同语义)。
// 返回值按 sort 升序 → id 升序排好。
func pickAgentsV3(app string, accept func(t string) bool) ([]*comm.AgentConfig, error) {
rows := make([]*comm.AgentConfig, 0)
if err := postgres.Find(comm.TableAgentConfig, &rows, "(app_name=? OR app_name='')", app); err != nil && err != postgres.ErrNoDocuments {
return nil, err
}
// 同 id 应用行覆盖全局行;仅启用的(应用行停用则回退全局行,与第三方服务下发同语义)。
picked := map[string]*comm.AgentConfig{}
for _, a := range rows {
if !a.Enable || !comm.AgentTypeSelectable(a.Type) {
continue // 翻译类已下线:库里可能还有历史行,但不下发给新客户端
if !a.Enable || !accept(a.Type) {
continue
}
if exist, ok := picked[a.Id]; !ok || (exist.AppName == "" && a.AppName == app && app != "") {
picked[a.Id] = a
@ -63,10 +77,14 @@ func (this *apiV3Comp) agentItemsV3() ([]*pb.AgentItemV3, error) {
}
return list[i].Id < list[j].Id
})
return list, nil
}
providerOf := this.svcProviders(app)
items := make([]*pb.AgentItemV3, 0, len(list))
for _, a := range list {
// agentItemV3Of 把一行 agent 配置转成下发结构。对话类与翻译类共用它——
// 两类在后台是同一张表、同一套编辑器,客户端消费的也都是「服务编排 + 音色」,
// 只是类型用不到的服务槽位留空(如对话类的 mt/ast、翻译类的 llm/sts)。
// provider 是该 agent 负责出声的服务的服务商,用于按内置音色表补全 voice_map(见 voiceSvcIdOf)。
func agentItemV3Of(a *comm.AgentConfig, provider string) *pb.AgentItemV3 {
item := &pb.AgentItemV3{
Id: a.Id,
Name: a.Name,
@ -77,6 +95,8 @@ func (this *apiV3Comp) agentItemsV3() ([]*pb.AgentItemV3, error) {
LlmSvcId: a.LlmSvcId,
TtsSvcId: a.TtsSvcId,
StsSvcId: a.StsSvcId,
MtSvcId: a.MtSvcId,
AstSvcId: a.AstSvcId,
SystemPrompt: a.SystemPrompt,
Gender: a.Gender,
GreetingEnabled: a.GreetingEnabled,
@ -86,7 +106,7 @@ func (this *apiV3Comp) agentItemsV3() ([]*pb.AgentItemV3, error) {
McpSvcIds: append([]string{}, a.McpSvcIds...),
Mcps: agentMcpBindings(a),
Sort: a.Sort,
VoiceMap: resolveAgentVoices(a, providerOf[voiceSvcIdOf(a)]),
VoiceMap: resolveAgentVoices(a, provider),
Variables: make([]*pb.AgentVariableItem, 0, len(a.Variables)),
}
for _, v := range a.Variables {
@ -94,9 +114,7 @@ func (this *apiV3Comp) agentItemsV3() ([]*pb.AgentItemV3, error) {
Key: v.Key, Label: v.Label, DefValue: v.DefValue, Description: v.Description,
})
}
items = append(items, item)
}
return items, nil
return item
}
// agentMcpBindings 把 agent 的 MCP 绑定展开成「服务 + 该 agent 允许的工具」。
@ -132,10 +150,14 @@ func (this *apiV3Comp) svcProviders(app string) map[string]string {
return out
}
// voiceSvcIdOf 取 agent 负责出声的服务:对话类是 TTS,端到端对话由 STS 服务自己合成。
// voiceSvcIdOf 取 agent 负责出声的服务:三段式(chat / translate)是 TTS,
// 端到端由 STS / AST 服务自己合成(口径同 admin agents.vue 的 voiceSvcId)。
func voiceSvcIdOf(a *comm.AgentConfig) string {
if a.Type == comm.AgentTypeStsChat {
switch a.Type {
case comm.AgentTypeStsChat:
return a.StsSvcId
case comm.AgentTypeAstTranslate:
return a.AstSvcId
}
return a.TtsSvcId
}

5
apps/services/modules/user/api_v3_getappconfig.go

@ -9,9 +9,10 @@ import (
)
// @Summary 获取应用配置(v3)
// @Description 一次性下发客户端启动所需的全部配置:环境变量 + 智能体 + 产品 + 第三方服务(含 MCP) + 会议记录能力。
// @Description 一次性下发客户端启动所需的全部配置:环境变量 + 智能体 + 产品 + 第三方服务(含 MCP) + 会议记录能力 + 通话翻译配置。
// 与 v2 的差别:不再单独下发 mcps(MCP 已并入第三方服务配置,在 thirdsvcs 里按 categories 含 "10" 识别);
// 新增 meet(会议记录支持的语言与可选总结大模型,来自后台「会议记录服务」编排);
// 新增 call_translate(通话翻译的选路规则 + 规则引用的翻译 agent,已按用户所在区域筛过);
// agents 换成与 user_getagents_v3 相同的结构(含 type=chat|sts-chat 与 stt/llm/tts/sts 服务编排、
// MCP 绑定、变量、已补全的音色表),不再下发旧 agent 表。v1/v2 保持原样不受影响。
// @Tags User
@ -69,6 +70,8 @@ func (this *apiV3Comp) GetAppConfig(session comm.IUserSession, req *pb.UserGetAp
Products: products,
Thirdsvcs: thirdsvcs,
Meet: this.meetCapabilities(),
// 通话翻译:按上面解析出的区域筛好的选路规则 + 规则引用的翻译 agent(见 api_v3_calltranslate.go)。
CallTranslate: this.callTranslateV3(region),
}
return
}

79
apps/services/modules/user/module_test.go

@ -176,7 +176,7 @@ func TestV3MeetCapabilitiesEmptySnapshot(t *testing.T) {
}
}
// v3 的响应契约:不再有 mcps(并入 thirdsvcs),必须有 meet。
// v3 的响应契约:不再有 mcps(并入 thirdsvcs),必须有 meet 与 call_translate。
// pb 重新生成或有人手改 proto 把字段动了,这里先炸,而不是等客户端解析失败。
func TestV3AppConfigRespContract(t *testing.T) {
resp := &pb.UserGetAppConfigV3Resp{
@ -185,6 +185,10 @@ func TestV3AppConfigRespContract(t *testing.T) {
Products: []*pb.DBProduct{},
Thirdsvcs: []*pb.ThirdSvcItem{},
Meet: newV3Comp(nil).meetCapabilities(),
CallTranslate: &pb.CallTranslateItem{
Rules: []*pb.CallTranslateRuleItem{},
Agents: []*pb.AgentItemV3{},
},
}
buf, err := json.Marshal(resp)
if err != nil {
@ -194,7 +198,7 @@ func TestV3AppConfigRespContract(t *testing.T) {
if err = json.Unmarshal(buf, &got); err != nil {
t.Fatal(err)
}
for _, k := range []string{"env", "agents", "products", "thirdsvcs", "meet"} {
for _, k := range []string{"env", "agents", "products", "thirdsvcs", "meet", "call_translate"} {
if _, ok := got[k]; !ok {
t.Fatalf("v3 响应缺字段 %s:%s", k, buf)
}
@ -204,36 +208,6 @@ func TestV3AppConfigRespContract(t *testing.T) {
}
}
// appconfig 里的 agent 必须是「能直接跑起来」的那一份:带 type(chat|sts-chat) 与服务编排。
// 之前这里下发的是旧 agent 表(DBAgent),客户端拿到 agent 却不知道走哪条链路、调哪个服务。
func TestV3AppConfigAgentCarriesTypeAndServices(t *testing.T) {
resp := &pb.UserGetAppConfigV3Resp{Agents: []*pb.AgentItemV3{{
Id: "chat-abc", Name: "口语搭子", Type: comm.AgentTypeChat,
SttSvcId: "svc_stt", LlmSvcId: "svc_llm", TtsSvcId: "svc_tts",
}}}
buf, err := json.Marshal(resp)
if err != nil {
t.Fatal(err)
}
var got struct {
Agents []map[string]any `json:"agents"`
}
if err = json.Unmarshal(buf, &got); err != nil {
t.Fatal(err)
}
if len(got.Agents) != 1 {
t.Fatalf("agents 解析失败:%s", buf)
}
for _, k := range []string{"type", "stt_svc_id", "llm_svc_id", "tts_svc_id", "sts_svc_id", "mcp_svc_ids", "variables", "voice_map"} {
if _, ok := got.Agents[0][k]; !ok {
t.Fatalf("appconfig 的 agent 缺字段 %s:%s", k, buf)
}
}
if got.Agents[0]["type"] != comm.AgentTypeChat {
t.Fatalf("agent type 没下发对:%s", buf)
}
}
// 客户端靠 categories 含 "10" 从 thirdsvcs 里挑出 MCP 服务——v3 去掉 mcps 后这是唯一识别方式。
func TestV3McpIdentifiedByCategories(t *testing.T) {
svcs := []*pb.ThirdSvcItem{
@ -338,14 +312,20 @@ func TestResolveAgentVoicesSkipsUnknown(t *testing.T) {
}
// 出声的是哪个服务:对话类看 TTS,端到端对话看 STS(音色候选按服务商区分,取错就全错)。
// 三段式(chat / translate)出声靠 TTS,端到端由 STS / AST 服务自己合成。
// 取错服务就会拿别家服务商的音色表去补全 voice_map,下发一串对端根本不认的音色名。
func TestVoiceSvcIdOf(t *testing.T) {
chat := &comm.AgentConfig{Type: comm.AgentTypeChat, TtsSvcId: "tts_azure", StsSvcId: "sts_x"}
if got := voiceSvcIdOf(chat); got != "tts_azure" {
t.Fatalf("chat 应取 tts_svc_id,实际 %s", got)
a := &comm.AgentConfig{TtsSvcId: "tts_azure", StsSvcId: "sts_x", AstSvcId: "ast_y"}
for _, c := range []struct{ typ, want string }{
{comm.AgentTypeChat, "tts_azure"},
{comm.AgentTypeTranslate, "tts_azure"},
{comm.AgentTypeStsChat, "sts_x"},
{comm.AgentTypeAstTranslate, "ast_y"},
} {
a.Type = c.typ
if got := voiceSvcIdOf(a); got != c.want {
t.Fatalf("%s 应取 %s,实际 %s", c.typ, c.want, got)
}
sts := &comm.AgentConfig{Type: comm.AgentTypeStsChat, TtsSvcId: "tts_azure", StsSvcId: "sts_x"}
if got := voiceSvcIdOf(sts); got != "sts_x" {
t.Fatalf("sts-chat 应取 sts_svc_id,实际 %s", got)
}
}
@ -635,11 +615,11 @@ func TestApiProbeAppConfigV3(t *testing.T) {
if testing.Short() {
t.Skip("联调探针会打线上接口,-short 下跳过")
}
// ↓↓↓ 换环境/换账号只改这三行 ↓↓↓(当前 = 云雁测试环境 app-dev / 8.138.86.223)
// ↓↓↓ 换环境/换账号只改这三行 ↓↓↓
const (
url = "https://app-dev.voitrans.net/api/home/user_getappconfig_v3"
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJFYXJwaG9uZSIsInN1YiI6IlNnaW4iLCJuYmYiOjE3ODcwNDUwMTksImlhdCI6MTc4NzA0NTAxOSwianRpIjoiMjA4ODQ1NDYxNjI2MDgwNDYwOCJ9.jhhyUqi4KdjnLJ0ngEfsd5G6nXRJ94tbDo_G4rsfDy8"
key = "83KNTeWWQ4GiQHhINJH1H8RyN88HNUOf" // 该环境 .env 里的 GATEWAY_ENCRYPT_KEY
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJFYXJwaG9uZSIsInN1YiI6IlNnaW4iLCJuYmYiOjE3ODY3NjEzNTYsImlhdCI6MTc4Njc2MTM1NiwianRpIjoiMjA4ODQ1NDYxNjI2MDgwNDYwOCJ9.tAkiUVNNEJMF4_Gfl1qYseUmh73CWcU5Ls9govEOtEQ"
key = "83KNTeWwQ4GiQHhlNJH1H8RyN88HNUOf" // 该环境 .env 里的 GATEWAY_ENCRYPT_KEY
)
body := probeCall(t, url, token, key)
@ -665,23 +645,8 @@ func TestApiProbeAppConfigV3(t *testing.T) {
t.Logf(" svc[%s] name=%s provider=%s categories=%s fields=%d mcp=%v",
s.Id, s.Name, s.Provider, s.Categories, len(s.Fields), comm.CategoriesHasMCP(s.Categories))
}
// agent 要能「拿了就跑」:type 指明走 chat 还是 sts-chat,对应槽位的服务 id 得齐。
for _, a := range d.Agents {
t.Logf(" agent id=%s name=%s type=%s stt=%s llm=%s tts=%s sts=%s mcp=%v vars=%d voices=%d",
a.Id, a.Name, a.Type, a.SttSvcId, a.LlmSvcId, a.TtsSvcId, a.StsSvcId,
a.McpSvcIds, len(a.Variables), len(a.VoiceMap))
switch a.Type {
case comm.AgentTypeChat:
if a.SttSvcId == "" || a.LlmSvcId == "" || a.TtsSvcId == "" {
t.Fatalf("chat agent %s 缺服务编排:stt=%q llm=%q tts=%q", a.Id, a.SttSvcId, a.LlmSvcId, a.TtsSvcId)
}
case comm.AgentTypeStsChat:
if a.StsSvcId == "" {
t.Fatalf("sts-chat agent %s 缺 sts_svc_id", a.Id)
}
default:
t.Fatalf("agent %s 的 type 为 %q:客户端不知道该走哪条链路(下发的应是 chat|sts-chat)", a.Id, a.Type)
}
t.Logf(" agent id=%s name=%s", a.Id, a.Name)
}
// meet 是 v3 新增的:为空可以(后台没编排会议记录),为 null 不行——客户端会崩。

275
apps/services/pb/pay_msg.pb.go

@ -102,6 +102,89 @@ func (x *PayGetGoodssResp) GetGoodss() []*DBGoods {
return nil
}
// 读取商品列表 v2:改读**管理后台的应用商品配置**(console 公共库 app_goods,按应用名隔离),
// 而不是各业务库自己维护的 goods 表;口径与支付通知 v2(pay_paypalnotifyv2)发货一致。
type PayGetGoodssV2Req struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PayGetGoodssV2Req) Reset() {
*x = PayGetGoodssV2Req{}
mi := &file_pay_pay_msg_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PayGetGoodssV2Req) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PayGetGoodssV2Req) ProtoMessage() {}
func (x *PayGetGoodssV2Req) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PayGetGoodssV2Req.ProtoReflect.Descriptor instead.
func (*PayGetGoodssV2Req) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{2}
}
// 响应沿用 DBGoods 结构,客户端解析与 v1 完全一致(id = 后台配置的商品标识 goods_id)。
type PayGetGoodssV2Resp struct {
state protoimpl.MessageState `protogen:"open.v1"`
Goodss []*DBGoods `protobuf:"bytes,1,rep,name=goodss,proto3" json:"goodss,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PayGetGoodssV2Resp) Reset() {
*x = PayGetGoodssV2Resp{}
mi := &file_pay_pay_msg_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PayGetGoodssV2Resp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PayGetGoodssV2Resp) ProtoMessage() {}
func (x *PayGetGoodssV2Resp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PayGetGoodssV2Resp.ProtoReflect.Descriptor instead.
func (*PayGetGoodssV2Resp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{3}
}
func (x *PayGetGoodssV2Resp) GetGoodss() []*DBGoods {
if x != nil {
return x.Goodss
}
return nil
}
// 读取用户订单信息
type PayGetOrderListReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -113,7 +196,7 @@ type PayGetOrderListReq struct {
func (x *PayGetOrderListReq) Reset() {
*x = PayGetOrderListReq{}
mi := &file_pay_pay_msg_proto_msgTypes[2]
mi := &file_pay_pay_msg_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -125,7 +208,7 @@ func (x *PayGetOrderListReq) String() string {
func (*PayGetOrderListReq) ProtoMessage() {}
func (x *PayGetOrderListReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[2]
mi := &file_pay_pay_msg_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -138,7 +221,7 @@ func (x *PayGetOrderListReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayGetOrderListReq.ProtoReflect.Descriptor instead.
func (*PayGetOrderListReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{2}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{4}
}
func (x *PayGetOrderListReq) GetPage() int32 {
@ -165,7 +248,7 @@ type PayGetOrderListResp struct {
func (x *PayGetOrderListResp) Reset() {
*x = PayGetOrderListResp{}
mi := &file_pay_pay_msg_proto_msgTypes[3]
mi := &file_pay_pay_msg_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -177,7 +260,7 @@ func (x *PayGetOrderListResp) String() string {
func (*PayGetOrderListResp) ProtoMessage() {}
func (x *PayGetOrderListResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[3]
mi := &file_pay_pay_msg_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -190,7 +273,7 @@ func (x *PayGetOrderListResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayGetOrderListResp.ProtoReflect.Descriptor instead.
func (*PayGetOrderListResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{3}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{5}
}
func (x *PayGetOrderListResp) GetOrderss() []*DBPayOrder {
@ -230,7 +313,7 @@ type PayCreatePayOrderReq struct {
func (x *PayCreatePayOrderReq) Reset() {
*x = PayCreatePayOrderReq{}
mi := &file_pay_pay_msg_proto_msgTypes[4]
mi := &file_pay_pay_msg_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -242,7 +325,7 @@ func (x *PayCreatePayOrderReq) String() string {
func (*PayCreatePayOrderReq) ProtoMessage() {}
func (x *PayCreatePayOrderReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[4]
mi := &file_pay_pay_msg_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -255,7 +338,7 @@ func (x *PayCreatePayOrderReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayCreatePayOrderReq.ProtoReflect.Descriptor instead.
func (*PayCreatePayOrderReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{4}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{6}
}
func (x *PayCreatePayOrderReq) GetProductId() string {
@ -349,7 +432,7 @@ type PayCreatePayOrderResp struct {
func (x *PayCreatePayOrderResp) Reset() {
*x = PayCreatePayOrderResp{}
mi := &file_pay_pay_msg_proto_msgTypes[5]
mi := &file_pay_pay_msg_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -361,7 +444,7 @@ func (x *PayCreatePayOrderResp) String() string {
func (*PayCreatePayOrderResp) ProtoMessage() {}
func (x *PayCreatePayOrderResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[5]
mi := &file_pay_pay_msg_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -374,7 +457,7 @@ func (x *PayCreatePayOrderResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayCreatePayOrderResp.ProtoReflect.Descriptor instead.
func (*PayCreatePayOrderResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{5}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{7}
}
func (x *PayCreatePayOrderResp) GetOrderid() string {
@ -436,7 +519,7 @@ type PayAskOrCancelReq struct {
func (x *PayAskOrCancelReq) Reset() {
*x = PayAskOrCancelReq{}
mi := &file_pay_pay_msg_proto_msgTypes[6]
mi := &file_pay_pay_msg_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -448,7 +531,7 @@ func (x *PayAskOrCancelReq) String() string {
func (*PayAskOrCancelReq) ProtoMessage() {}
func (x *PayAskOrCancelReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[6]
mi := &file_pay_pay_msg_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -461,7 +544,7 @@ func (x *PayAskOrCancelReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayAskOrCancelReq.ProtoReflect.Descriptor instead.
func (*PayAskOrCancelReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{6}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{8}
}
func (x *PayAskOrCancelReq) GetOrderid() string {
@ -523,7 +606,7 @@ type PayPayOrderAskResp struct {
func (x *PayPayOrderAskResp) Reset() {
*x = PayPayOrderAskResp{}
mi := &file_pay_pay_msg_proto_msgTypes[7]
mi := &file_pay_pay_msg_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -535,7 +618,7 @@ func (x *PayPayOrderAskResp) String() string {
func (*PayPayOrderAskResp) ProtoMessage() {}
func (x *PayPayOrderAskResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[7]
mi := &file_pay_pay_msg_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -548,7 +631,7 @@ func (x *PayPayOrderAskResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayPayOrderAskResp.ProtoReflect.Descriptor instead.
func (*PayPayOrderAskResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{7}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{9}
}
func (x *PayPayOrderAskResp) GetOrder() *DBPayOrder {
@ -579,7 +662,7 @@ type PayWeChatNotifyReq struct {
func (x *PayWeChatNotifyReq) Reset() {
*x = PayWeChatNotifyReq{}
mi := &file_pay_pay_msg_proto_msgTypes[8]
mi := &file_pay_pay_msg_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -591,7 +674,7 @@ func (x *PayWeChatNotifyReq) String() string {
func (*PayWeChatNotifyReq) ProtoMessage() {}
func (x *PayWeChatNotifyReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[8]
mi := &file_pay_pay_msg_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -604,7 +687,7 @@ func (x *PayWeChatNotifyReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayWeChatNotifyReq.ProtoReflect.Descriptor instead.
func (*PayWeChatNotifyReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{8}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{10}
}
func (x *PayWeChatNotifyReq) GetId() string {
@ -657,7 +740,7 @@ type PayWeChatNotifyResp struct {
func (x *PayWeChatNotifyResp) Reset() {
*x = PayWeChatNotifyResp{}
mi := &file_pay_pay_msg_proto_msgTypes[9]
mi := &file_pay_pay_msg_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -669,7 +752,7 @@ func (x *PayWeChatNotifyResp) String() string {
func (*PayWeChatNotifyResp) ProtoMessage() {}
func (x *PayWeChatNotifyResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[9]
mi := &file_pay_pay_msg_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -682,7 +765,7 @@ func (x *PayWeChatNotifyResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayWeChatNotifyResp.ProtoReflect.Descriptor instead.
func (*PayWeChatNotifyResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{9}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{11}
}
// 加密资源结构(包含需解密的核心交易数据)
@ -698,7 +781,7 @@ type Resource struct {
func (x *Resource) Reset() {
*x = Resource{}
mi := &file_pay_pay_msg_proto_msgTypes[10]
mi := &file_pay_pay_msg_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -710,7 +793,7 @@ func (x *Resource) String() string {
func (*Resource) ProtoMessage() {}
func (x *Resource) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[10]
mi := &file_pay_pay_msg_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -723,7 +806,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message {
// Deprecated: Use Resource.ProtoReflect.Descriptor instead.
func (*Resource) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{10}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{12}
}
func (x *Resource) GetAlgorithm() string {
@ -784,7 +867,7 @@ type WxPayTransaction struct {
func (x *WxPayTransaction) Reset() {
*x = WxPayTransaction{}
mi := &file_pay_pay_msg_proto_msgTypes[11]
mi := &file_pay_pay_msg_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -796,7 +879,7 @@ func (x *WxPayTransaction) String() string {
func (*WxPayTransaction) ProtoMessage() {}
func (x *WxPayTransaction) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[11]
mi := &file_pay_pay_msg_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -809,7 +892,7 @@ func (x *WxPayTransaction) ProtoReflect() protoreflect.Message {
// Deprecated: Use WxPayTransaction.ProtoReflect.Descriptor instead.
func (*WxPayTransaction) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{11}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{13}
}
func (x *WxPayTransaction) GetOutTradeNo() string {
@ -937,7 +1020,7 @@ type Amount struct {
func (x *Amount) Reset() {
*x = Amount{}
mi := &file_pay_pay_msg_proto_msgTypes[12]
mi := &file_pay_pay_msg_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -949,7 +1032,7 @@ func (x *Amount) String() string {
func (*Amount) ProtoMessage() {}
func (x *Amount) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[12]
mi := &file_pay_pay_msg_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -962,7 +1045,7 @@ func (x *Amount) ProtoReflect() protoreflect.Message {
// Deprecated: Use Amount.ProtoReflect.Descriptor instead.
func (*Amount) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{12}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{14}
}
func (x *Amount) GetTotal() int64 {
@ -1003,7 +1086,7 @@ type Payer struct {
func (x *Payer) Reset() {
*x = Payer{}
mi := &file_pay_pay_msg_proto_msgTypes[13]
mi := &file_pay_pay_msg_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1015,7 +1098,7 @@ func (x *Payer) String() string {
func (*Payer) ProtoMessage() {}
func (x *Payer) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[13]
mi := &file_pay_pay_msg_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1028,7 +1111,7 @@ func (x *Payer) ProtoReflect() protoreflect.Message {
// Deprecated: Use Payer.ProtoReflect.Descriptor instead.
func (*Payer) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{13}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{15}
}
func (x *Payer) GetOpenid() string {
@ -1072,7 +1155,7 @@ type PayAlipayNotifyReq struct {
func (x *PayAlipayNotifyReq) Reset() {
*x = PayAlipayNotifyReq{}
mi := &file_pay_pay_msg_proto_msgTypes[14]
mi := &file_pay_pay_msg_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1084,7 +1167,7 @@ func (x *PayAlipayNotifyReq) String() string {
func (*PayAlipayNotifyReq) ProtoMessage() {}
func (x *PayAlipayNotifyReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[14]
mi := &file_pay_pay_msg_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1097,7 +1180,7 @@ func (x *PayAlipayNotifyReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayAlipayNotifyReq.ProtoReflect.Descriptor instead.
func (*PayAlipayNotifyReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{14}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{16}
}
func (x *PayAlipayNotifyReq) GetAppId() string {
@ -1283,7 +1366,7 @@ type PayAlipayNotifyResp struct {
func (x *PayAlipayNotifyResp) Reset() {
*x = PayAlipayNotifyResp{}
mi := &file_pay_pay_msg_proto_msgTypes[15]
mi := &file_pay_pay_msg_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1295,7 +1378,7 @@ func (x *PayAlipayNotifyResp) String() string {
func (*PayAlipayNotifyResp) ProtoMessage() {}
func (x *PayAlipayNotifyResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[15]
mi := &file_pay_pay_msg_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1308,7 +1391,7 @@ func (x *PayAlipayNotifyResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayAlipayNotifyResp.ProtoReflect.Descriptor instead.
func (*PayAlipayNotifyResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{15}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{17}
}
// PayPal 异步通知参数(包含验签所需头和原始 body)
@ -1327,7 +1410,7 @@ type PayPayPalNotifyReq struct {
func (x *PayPayPalNotifyReq) Reset() {
*x = PayPayPalNotifyReq{}
mi := &file_pay_pay_msg_proto_msgTypes[16]
mi := &file_pay_pay_msg_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1339,7 +1422,7 @@ func (x *PayPayPalNotifyReq) String() string {
func (*PayPayPalNotifyReq) ProtoMessage() {}
func (x *PayPayPalNotifyReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[16]
mi := &file_pay_pay_msg_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1352,7 +1435,7 @@ func (x *PayPayPalNotifyReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayPayPalNotifyReq.ProtoReflect.Descriptor instead.
func (*PayPayPalNotifyReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{16}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{18}
}
func (x *PayPayPalNotifyReq) GetTransmissionId() string {
@ -1414,7 +1497,7 @@ type PayPayPalNotifyResp struct {
func (x *PayPayPalNotifyResp) Reset() {
*x = PayPayPalNotifyResp{}
mi := &file_pay_pay_msg_proto_msgTypes[17]
mi := &file_pay_pay_msg_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1426,7 +1509,7 @@ func (x *PayPayPalNotifyResp) String() string {
func (*PayPayPalNotifyResp) ProtoMessage() {}
func (x *PayPayPalNotifyResp) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[17]
mi := &file_pay_pay_msg_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1439,7 +1522,7 @@ func (x *PayPayPalNotifyResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayPayPalNotifyResp.ProtoReflect.Descriptor instead.
func (*PayPayPalNotifyResp) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{17}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{19}
}
func (x *PayPayPalNotifyResp) GetCode() string {
@ -1471,7 +1554,7 @@ type PayAppleIAPNotifyReq struct {
func (x *PayAppleIAPNotifyReq) Reset() {
*x = PayAppleIAPNotifyReq{}
mi := &file_pay_pay_msg_proto_msgTypes[18]
mi := &file_pay_pay_msg_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1483,7 +1566,7 @@ func (x *PayAppleIAPNotifyReq) String() string {
func (*PayAppleIAPNotifyReq) ProtoMessage() {}
func (x *PayAppleIAPNotifyReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[18]
mi := &file_pay_pay_msg_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1496,7 +1579,7 @@ func (x *PayAppleIAPNotifyReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayAppleIAPNotifyReq.ProtoReflect.Descriptor instead.
func (*PayAppleIAPNotifyReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{18}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{20}
}
func (x *PayAppleIAPNotifyReq) GetUid() string {
@ -1556,7 +1639,7 @@ type PayGoogleIAPNotifyReq struct {
func (x *PayGoogleIAPNotifyReq) Reset() {
*x = PayGoogleIAPNotifyReq{}
mi := &file_pay_pay_msg_proto_msgTypes[19]
mi := &file_pay_pay_msg_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1568,7 +1651,7 @@ func (x *PayGoogleIAPNotifyReq) String() string {
func (*PayGoogleIAPNotifyReq) ProtoMessage() {}
func (x *PayGoogleIAPNotifyReq) ProtoReflect() protoreflect.Message {
mi := &file_pay_pay_msg_proto_msgTypes[19]
mi := &file_pay_pay_msg_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1581,7 +1664,7 @@ func (x *PayGoogleIAPNotifyReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use PayGoogleIAPNotifyReq.ProtoReflect.Descriptor instead.
func (*PayGoogleIAPNotifyReq) Descriptor() ([]byte, []int) {
return file_pay_pay_msg_proto_rawDescGZIP(), []int{19}
return file_pay_pay_msg_proto_rawDescGZIP(), []int{21}
}
func (x *PayGoogleIAPNotifyReq) GetUid() string {
@ -1633,6 +1716,9 @@ const file_pay_pay_msg_proto_rawDesc = "" +
"\x11pay/pay_msg.proto\x1a\bdb.proto\x1a\x10pay/pay_db.proto\"\x11\n" +
"\x0fPayGetGoodssReq\"4\n" +
"\x10PayGetGoodssResp\x12 \n" +
"\x06goodss\x18\x01 \x03(\v2\b.DBGoodsR\x06goodss\"\x13\n" +
"\x11PayGetGoodssV2Req\"6\n" +
"\x12PayGetGoodssV2Resp\x12 \n" +
"\x06goodss\x18\x01 \x03(\v2\b.DBGoodsR\x06goodss\"<\n" +
"\x12PayGetOrderListReq\x12\x12\n" +
"\x04page\x18\x01 \x01(\x05R\x04page\x12\x12\n" +
@ -1802,49 +1888,52 @@ func file_pay_pay_msg_proto_rawDescGZIP() []byte {
return file_pay_pay_msg_proto_rawDescData
}
var file_pay_pay_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
var file_pay_pay_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
var file_pay_pay_msg_proto_goTypes = []any{
(*PayGetGoodssReq)(nil), // 0: PayGetGoodssReq
(*PayGetGoodssResp)(nil), // 1: PayGetGoodssResp
(*PayGetOrderListReq)(nil), // 2: PayGetOrderListReq
(*PayGetOrderListResp)(nil), // 3: PayGetOrderListResp
(*PayCreatePayOrderReq)(nil), // 4: PayCreatePayOrderReq
(*PayCreatePayOrderResp)(nil), // 5: PayCreatePayOrderResp
(*PayAskOrCancelReq)(nil), // 6: PayAskOrCancelReq
(*PayPayOrderAskResp)(nil), // 7: PayPayOrderAskResp
(*PayWeChatNotifyReq)(nil), // 8: PayWeChatNotifyReq
(*PayWeChatNotifyResp)(nil), // 9: PayWeChatNotifyResp
(*Resource)(nil), // 10: Resource
(*WxPayTransaction)(nil), // 11: WxPayTransaction
(*Amount)(nil), // 12: Amount
(*Payer)(nil), // 13: Payer
(*PayAlipayNotifyReq)(nil), // 14: PayAlipayNotifyReq
(*PayAlipayNotifyResp)(nil), // 15: PayAlipayNotifyResp
(*PayPayPalNotifyReq)(nil), // 16: PayPayPalNotifyReq
(*PayPayPalNotifyResp)(nil), // 17: PayPayPalNotifyResp
(*PayAppleIAPNotifyReq)(nil), // 18: PayAppleIAPNotifyReq
(*PayGoogleIAPNotifyReq)(nil), // 19: PayGoogleIAPNotifyReq
(*DBGoods)(nil), // 20: DBGoods
(*DBPayOrder)(nil), // 21: DBPayOrder
(PaymentMethod)(0), // 22: PaymentMethod
(*AppPayParams)(nil), // 23: AppPayParams
(*DBUser)(nil), // 24: DBUser
(*PayGetGoodssV2Req)(nil), // 2: PayGetGoodssV2Req
(*PayGetGoodssV2Resp)(nil), // 3: PayGetGoodssV2Resp
(*PayGetOrderListReq)(nil), // 4: PayGetOrderListReq
(*PayGetOrderListResp)(nil), // 5: PayGetOrderListResp
(*PayCreatePayOrderReq)(nil), // 6: PayCreatePayOrderReq
(*PayCreatePayOrderResp)(nil), // 7: PayCreatePayOrderResp
(*PayAskOrCancelReq)(nil), // 8: PayAskOrCancelReq
(*PayPayOrderAskResp)(nil), // 9: PayPayOrderAskResp
(*PayWeChatNotifyReq)(nil), // 10: PayWeChatNotifyReq
(*PayWeChatNotifyResp)(nil), // 11: PayWeChatNotifyResp
(*Resource)(nil), // 12: Resource
(*WxPayTransaction)(nil), // 13: WxPayTransaction
(*Amount)(nil), // 14: Amount
(*Payer)(nil), // 15: Payer
(*PayAlipayNotifyReq)(nil), // 16: PayAlipayNotifyReq
(*PayAlipayNotifyResp)(nil), // 17: PayAlipayNotifyResp
(*PayPayPalNotifyReq)(nil), // 18: PayPayPalNotifyReq
(*PayPayPalNotifyResp)(nil), // 19: PayPayPalNotifyResp
(*PayAppleIAPNotifyReq)(nil), // 20: PayAppleIAPNotifyReq
(*PayGoogleIAPNotifyReq)(nil), // 21: PayGoogleIAPNotifyReq
(*DBGoods)(nil), // 22: DBGoods
(*DBPayOrder)(nil), // 23: DBPayOrder
(PaymentMethod)(0), // 24: PaymentMethod
(*AppPayParams)(nil), // 25: AppPayParams
(*DBUser)(nil), // 26: DBUser
}
var file_pay_pay_msg_proto_depIdxs = []int32{
20, // 0: PayGetGoodssResp.goodss:type_name -> DBGoods
21, // 1: PayGetOrderListResp.orderss:type_name -> DBPayOrder
22, // 2: PayCreatePayOrderReq.payment_method:type_name -> PaymentMethod
23, // 3: PayCreatePayOrderResp.pay_params:type_name -> AppPayParams
21, // 4: PayPayOrderAskResp.order:type_name -> DBPayOrder
24, // 5: PayPayOrderAskResp.user:type_name -> DBUser
10, // 6: PayWeChatNotifyReq.resource:type_name -> Resource
12, // 7: WxPayTransaction.amount:type_name -> Amount
13, // 8: WxPayTransaction.payer:type_name -> Payer
9, // [9:9] is the sub-list for method output_type
9, // [9:9] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
22, // 0: PayGetGoodssResp.goodss:type_name -> DBGoods
22, // 1: PayGetGoodssV2Resp.goodss:type_name -> DBGoods
23, // 2: PayGetOrderListResp.orderss:type_name -> DBPayOrder
24, // 3: PayCreatePayOrderReq.payment_method:type_name -> PaymentMethod
25, // 4: PayCreatePayOrderResp.pay_params:type_name -> AppPayParams
23, // 5: PayPayOrderAskResp.order:type_name -> DBPayOrder
26, // 6: PayPayOrderAskResp.user:type_name -> DBUser
12, // 7: PayWeChatNotifyReq.resource:type_name -> Resource
14, // 8: WxPayTransaction.amount:type_name -> Amount
15, // 9: WxPayTransaction.payer:type_name -> Payer
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_pay_pay_msg_proto_init() }
@ -1860,7 +1949,7 @@ func file_pay_pay_msg_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_pay_pay_msg_proto_rawDesc), len(file_pay_pay_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 20,
NumMessages: 22,
NumExtensions: 0,
NumServices: 0,
},

291
apps/services/pb/user_msg_v3.pb.go

@ -210,6 +210,7 @@ type UserGetAppConfigV3Resp struct {
Products []*DBProduct `protobuf:"bytes,3,rep,name=products,proto3" json:"products"` //@go_tags(`json:"products"`) 产品列表
Thirdsvcs []*ThirdSvcItem `protobuf:"bytes,4,rep,name=thirdsvcs,proto3" json:"thirdsvcs"` //@go_tags(`json:"thirdsvcs"`) 第三方服务(含 MCP 类,按用户区域合并、凭据已解密为明文)
Meet *MeetCapabilitiesItem `protobuf:"bytes,5,opt,name=meet,proto3" json:"meet"` //@go_tags(`json:"meet"`) 会议记录能力(语言 + 总结大模型)
CallTranslate *CallTranslateItem `protobuf:"bytes,6,opt,name=call_translate,json=callTranslate,proto3" json:"call_translate"` //@go_tags(`json:"call_translate"`) 通话翻译配置(本区域生效的选路规则 + 规则引用的翻译 agent)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -279,10 +280,172 @@ func (x *UserGetAppConfigV3Resp) GetMeet() *MeetCapabilitiesItem {
return nil
}
func (x *UserGetAppConfigV3Resp) GetCallTranslate() *CallTranslateItem {
if x != nil {
return x.CallTranslate
}
return nil
}
// 通话翻译选路规则:一条规则覆盖「多源语言 × 多目标语言」,命中后走 agent_id 指定的翻译 agent。
type CallTranslateRuleItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) 规则 id(仅用于日志/排障)
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name"` //@go_tags(`json:"name"`) 规则名(仅便于识别)
SrcLangs string `protobuf:"bytes,3,opt,name=src_langs,json=srcLangs,proto3" json:"src_langs"` //@go_tags(`json:"src_langs"`) 源语言集合,逗号分隔 BCP-47;单个 "*" = 任意语言
DstLangs string `protobuf:"bytes,4,opt,name=dst_langs,json=dstLangs,proto3" json:"dst_langs"` //@go_tags(`json:"dst_langs"`) 目标语言集合,同上
Bidir bool `protobuf:"varint,5,opt,name=bidir,proto3" json:"bidir"` //@go_tags(`json:"bidir"`) true=反向(dst→src)也命中这条规则
AgentId string `protobuf:"bytes,6,opt,name=agent_id,json=agentId,proto3" json:"agent_id"` //@go_tags(`json:"agent_id"`) 命中后使用的翻译 agent(在同响应的 call_translate.agents 里)
Priority int32 `protobuf:"varint,7,opt,name=priority,proto3" json:"priority"` //@go_tags(`json:"priority"`) 优先级,越大越先匹配
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CallTranslateRuleItem) Reset() {
*x = CallTranslateRuleItem{}
mi := &file_user_user_msg_v3_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CallTranslateRuleItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CallTranslateRuleItem) ProtoMessage() {}
func (x *CallTranslateRuleItem) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CallTranslateRuleItem.ProtoReflect.Descriptor instead.
func (*CallTranslateRuleItem) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{4}
}
func (x *CallTranslateRuleItem) GetId() uint64 {
if x != nil {
return x.Id
}
return 0
}
func (x *CallTranslateRuleItem) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *CallTranslateRuleItem) GetSrcLangs() string {
if x != nil {
return x.SrcLangs
}
return ""
}
func (x *CallTranslateRuleItem) GetDstLangs() string {
if x != nil {
return x.DstLangs
}
return ""
}
func (x *CallTranslateRuleItem) GetBidir() bool {
if x != nil {
return x.Bidir
}
return false
}
func (x *CallTranslateRuleItem) GetAgentId() string {
if x != nil {
return x.AgentId
}
return ""
}
func (x *CallTranslateRuleItem) GetPriority() int32 {
if x != nil {
return x.Priority
}
return 0
}
// 通话翻译配置:规则集 + 规则引用到的翻译 agent。
//
// 匹配口径(与服务端 comm.MatchCallTranslateRule 完全一致,客户端照此实现即可):
// 1. 两侧都命中才算候选。单侧命中分三档:全等=2、主语言子标签相同(zh-CN 规则接住 zh/zh-TW)=1、被 "*" 接住=0;
// 规则的语言集合里取最好的一档。bidir 规则同时试反向,取更好的一次。
// 2. 候选间取最优:优先级大 > 两侧得分之和高 > 精确度高(src/dst 每一侧非 "*" 各记 1 分) > id 小。
// 3. rules 已按「优先级降序 → id 升序」排好,但**不能只取第一条命中的**——得分与精确度会改变名次。
// 4. 一条都不命中 = 该方向没配通话翻译。
type CallTranslateItem struct {
state protoimpl.MessageState `protogen:"open.v1"`
Rules []*CallTranslateRuleItem `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules"` //@go_tags(`json:"rules"`) 本区域生效的规则(已按优先级降序 → id 升序排好)
Agents []*AgentItemV3 `protobuf:"bytes,2,rep,name=agents,proto3" json:"agents"` //@go_tags(`json:"agents"`) 上面规则引用到的翻译 agent(type=translate|ast-translate),结构同 agents
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CallTranslateItem) Reset() {
*x = CallTranslateItem{}
mi := &file_user_user_msg_v3_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CallTranslateItem) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CallTranslateItem) ProtoMessage() {}
func (x *CallTranslateItem) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CallTranslateItem.ProtoReflect.Descriptor instead.
func (*CallTranslateItem) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{5}
}
func (x *CallTranslateItem) GetRules() []*CallTranslateRuleItem {
if x != nil {
return x.Rules
}
return nil
}
func (x *CallTranslateItem) GetAgents() []*AgentItemV3 {
if x != nil {
return x.Agents
}
return nil
}
// v3 agent 列表:相比 v2 多了「绑定的 MCP 服务 + 变量」,并且 voice_map 是**已补全**的——
// 服务端按内置音色表(comm/ttsvoice.go)把 agent 支持的每种语言都解析出最终音色,
// 客户端不必自带音色表,切语言时按 language 直接取即可。
// 只下发 chat / sts-chat 两类(翻译类已下线),故服务槽位也只保留这两类用得到的。
// 顶层 agents 只下发对话类(chat / sts-chat);翻译类 agent(translate / ast-translate) 复用同一结构,
// 但走另一条链路——它是通话翻译规则的服务编排载体,在 call_translate.agents 里下发。
// 故四类的服务槽位都在这个结构里,按 type 取用其中若干,用不到的为空串。
type UserGetAgentsV3Req struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
@ -291,7 +454,7 @@ type UserGetAgentsV3Req struct {
func (x *UserGetAgentsV3Req) Reset() {
*x = UserGetAgentsV3Req{}
mi := &file_user_user_msg_v3_proto_msgTypes[4]
mi := &file_user_user_msg_v3_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -303,7 +466,7 @@ func (x *UserGetAgentsV3Req) String() string {
func (*UserGetAgentsV3Req) ProtoMessage() {}
func (x *UserGetAgentsV3Req) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[4]
mi := &file_user_user_msg_v3_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -316,7 +479,7 @@ func (x *UserGetAgentsV3Req) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserGetAgentsV3Req.ProtoReflect.Descriptor instead.
func (*UserGetAgentsV3Req) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{4}
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{6}
}
// agent 变量项:提示词/开场白里的占位符,客户端替换后再用
@ -332,7 +495,7 @@ type AgentVariableItem struct {
func (x *AgentVariableItem) Reset() {
*x = AgentVariableItem{}
mi := &file_user_user_msg_v3_proto_msgTypes[5]
mi := &file_user_user_msg_v3_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -344,7 +507,7 @@ func (x *AgentVariableItem) String() string {
func (*AgentVariableItem) ProtoMessage() {}
func (x *AgentVariableItem) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[5]
mi := &file_user_user_msg_v3_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -357,7 +520,7 @@ func (x *AgentVariableItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentVariableItem.ProtoReflect.Descriptor instead.
func (*AgentVariableItem) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{5}
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{7}
}
func (x *AgentVariableItem) GetKey() string {
@ -401,7 +564,7 @@ type AgentMcpBindingItem struct {
func (x *AgentMcpBindingItem) Reset() {
*x = AgentMcpBindingItem{}
mi := &file_user_user_msg_v3_proto_msgTypes[6]
mi := &file_user_user_msg_v3_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -413,7 +576,7 @@ func (x *AgentMcpBindingItem) String() string {
func (*AgentMcpBindingItem) ProtoMessage() {}
func (x *AgentMcpBindingItem) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[6]
mi := &file_user_user_msg_v3_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -426,7 +589,7 @@ func (x *AgentMcpBindingItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentMcpBindingItem.ProtoReflect.Descriptor instead.
func (*AgentMcpBindingItem) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{6}
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{8}
}
func (x *AgentMcpBindingItem) GetSvcId() string {
@ -447,7 +610,7 @@ type AgentItemV3 struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) agent id
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name"` //@go_tags(`json:"name"`) 名称
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type"` //@go_tags(`json:"type"`) chat|sts-chat
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type"` //@go_tags(`json:"type"`) chat|sts-chat(call_translate.agents 里则为 translate|ast-translate)
Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description"` //@go_tags(`json:"description"`) 描述
AvatarUrl string `protobuf:"bytes,5,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url"` //@go_tags(`json:"avatar_url"`) 头像
SttSvcId string `protobuf:"bytes,6,opt,name=stt_svc_id,json=sttSvcId,proto3" json:"stt_svc_id"` //@go_tags(`json:"stt_svc_id"`) 识别服务(chat)
@ -465,13 +628,15 @@ type AgentItemV3 struct {
Variables []*AgentVariableItem `protobuf:"bytes,18,rep,name=variables,proto3" json:"variables"` //@go_tags(`json:"variables"`) 变量定义
Sort int32 `protobuf:"varint,19,opt,name=sort,proto3" json:"sort"` //@go_tags(`json:"sort"`) 排序
Mcps []*AgentMcpBindingItem `protobuf:"bytes,20,rep,name=mcps,proto3" json:"mcps"` //@go_tags(`json:"mcps"`) MCP 绑定(含每个服务允许的工具);mcp_svc_ids 是它的 id 平铺,保留给老客户端
MtSvcId string `protobuf:"bytes,21,opt,name=mt_svc_id,json=mtSvcId,proto3" json:"mt_svc_id"` //@go_tags(`json:"mt_svc_id"`) 机器翻译服务(translate 三段式翻译;对话类为空)
AstSvcId string `protobuf:"bytes,22,opt,name=ast_svc_id,json=astSvcId,proto3" json:"ast_svc_id"` //@go_tags(`json:"ast_svc_id"`) 端到端语音翻译服务(ast-translate;对话类为空)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AgentItemV3) Reset() {
*x = AgentItemV3{}
mi := &file_user_user_msg_v3_proto_msgTypes[7]
mi := &file_user_user_msg_v3_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -483,7 +648,7 @@ func (x *AgentItemV3) String() string {
func (*AgentItemV3) ProtoMessage() {}
func (x *AgentItemV3) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[7]
mi := &file_user_user_msg_v3_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -496,7 +661,7 @@ func (x *AgentItemV3) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentItemV3.ProtoReflect.Descriptor instead.
func (*AgentItemV3) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{7}
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{9}
}
func (x *AgentItemV3) GetId() string {
@ -639,6 +804,20 @@ func (x *AgentItemV3) GetMcps() []*AgentMcpBindingItem {
return nil
}
func (x *AgentItemV3) GetMtSvcId() string {
if x != nil {
return x.MtSvcId
}
return ""
}
func (x *AgentItemV3) GetAstSvcId() string {
if x != nil {
return x.AstSvcId
}
return ""
}
type UserGetAgentsV3Resp struct {
state protoimpl.MessageState `protogen:"open.v1"`
Agents []*AgentItemV3 `protobuf:"bytes,1,rep,name=agents,proto3" json:"agents"` //@go_tags(`json:"agents"`) agent 列表
@ -648,7 +827,7 @@ type UserGetAgentsV3Resp struct {
func (x *UserGetAgentsV3Resp) Reset() {
*x = UserGetAgentsV3Resp{}
mi := &file_user_user_msg_v3_proto_msgTypes[8]
mi := &file_user_user_msg_v3_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -660,7 +839,7 @@ func (x *UserGetAgentsV3Resp) String() string {
func (*UserGetAgentsV3Resp) ProtoMessage() {}
func (x *UserGetAgentsV3Resp) ProtoReflect() protoreflect.Message {
mi := &file_user_user_msg_v3_proto_msgTypes[8]
mi := &file_user_user_msg_v3_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -673,7 +852,7 @@ func (x *UserGetAgentsV3Resp) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserGetAgentsV3Resp.ProtoReflect.Descriptor instead.
func (*UserGetAgentsV3Resp) Descriptor() ([]byte, []int) {
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{8}
return file_user_user_msg_v3_proto_rawDescGZIP(), []int{10}
}
func (x *UserGetAgentsV3Resp) GetAgents() []*AgentItemV3 {
@ -700,17 +879,29 @@ const file_user_user_msg_v3_proto_rawDesc = "" +
"\x13translate_languages\x18\x03 \x03(\tR\x12translateLanguages\x124\n" +
"\x16translate_any_language\x18\x04 \x01(\bR\x14translateAnyLanguage\x122\n" +
"\x15summary_client_choice\x18\x05 \x01(\bR\x13summaryClientChoice\x12B\n" +
"\x10summary_services\x18\x06 \x03(\v2\x17.MeetSummaryServiceItemR\x0fsummaryServices\"\xaa\x02\n" +
"\x10summary_services\x18\x06 \x03(\v2\x17.MeetSummaryServiceItemR\x0fsummaryServices\"\xe5\x02\n" +
"\x16UserGetAppConfigV3Resp\x122\n" +
"\x03env\x18\x01 \x03(\v2 .UserGetAppConfigV3Resp.EnvEntryR\x03env\x12$\n" +
"\x06agents\x18\x02 \x03(\v2\f.AgentItemV3R\x06agents\x12&\n" +
"\bproducts\x18\x03 \x03(\v2\n" +
".DBProductR\bproducts\x12+\n" +
"\tthirdsvcs\x18\x04 \x03(\v2\r.ThirdSvcItemR\tthirdsvcs\x12)\n" +
"\x04meet\x18\x05 \x01(\v2\x15.MeetCapabilitiesItemR\x04meet\x1a6\n" +
"\x04meet\x18\x05 \x01(\v2\x15.MeetCapabilitiesItemR\x04meet\x129\n" +
"\x0ecall_translate\x18\x06 \x01(\v2\x12.CallTranslateItemR\rcallTranslate\x1a6\n" +
"\bEnvEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc2\x01\n" +
"\x15CallTranslateRuleItem\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x04R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
"\tsrc_langs\x18\x03 \x01(\tR\bsrcLangs\x12\x1b\n" +
"\tdst_langs\x18\x04 \x01(\tR\bdstLangs\x12\x14\n" +
"\x05bidir\x18\x05 \x01(\bR\x05bidir\x12\x19\n" +
"\bagent_id\x18\x06 \x01(\tR\aagentId\x12\x1a\n" +
"\bpriority\x18\a \x01(\x05R\bpriority\"g\n" +
"\x11CallTranslateItem\x12,\n" +
"\x05rules\x18\x01 \x03(\v2\x16.CallTranslateRuleItemR\x05rules\x12$\n" +
"\x06agents\x18\x02 \x03(\v2\f.AgentItemV3R\x06agents\"\x14\n" +
"\x12UserGetAgentsV3Req\"z\n" +
"\x11AgentVariableItem\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
@ -719,7 +910,7 @@ const file_user_user_msg_v3_proto_rawDesc = "" +
"\vdescription\x18\x04 \x01(\tR\vdescription\"B\n" +
"\x13AgentMcpBindingItem\x12\x15\n" +
"\x06svc_id\x18\x01 \x01(\tR\x05svcId\x12\x14\n" +
"\x05tools\x18\x02 \x03(\tR\x05tools\"\x91\x05\n" +
"\x05tools\x18\x02 \x03(\tR\x05tools\"\xcb\x05\n" +
"\vAgentItemV3\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
@ -746,7 +937,10 @@ const file_user_user_msg_v3_proto_rawDesc = "" +
"\vmcp_svc_ids\x18\x11 \x03(\tR\tmcpSvcIds\x120\n" +
"\tvariables\x18\x12 \x03(\v2\x12.AgentVariableItemR\tvariables\x12\x12\n" +
"\x04sort\x18\x13 \x01(\x05R\x04sort\x12(\n" +
"\x04mcps\x18\x14 \x03(\v2\x14.AgentMcpBindingItemR\x04mcps\";\n" +
"\x04mcps\x18\x14 \x03(\v2\x14.AgentMcpBindingItemR\x04mcps\x12\x1a\n" +
"\tmt_svc_id\x18\x15 \x01(\tR\amtSvcId\x12\x1c\n" +
"\n" +
"ast_svc_id\x18\x16 \x01(\tR\bastSvcId\";\n" +
"\x13UserGetAgentsV3Resp\x12$\n" +
"\x06agents\x18\x01 \x03(\v2\f.AgentItemV3R\x06agentsB\x06Z\x04.;pbb\x06proto3"
@ -762,38 +956,43 @@ func file_user_user_msg_v3_proto_rawDescGZIP() []byte {
return file_user_user_msg_v3_proto_rawDescData
}
var file_user_user_msg_v3_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_user_user_msg_v3_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_user_user_msg_v3_proto_goTypes = []any{
(*UserGetAppConfigV3Req)(nil), // 0: UserGetAppConfigV3Req
(*MeetSummaryServiceItem)(nil), // 1: MeetSummaryServiceItem
(*MeetCapabilitiesItem)(nil), // 2: MeetCapabilitiesItem
(*UserGetAppConfigV3Resp)(nil), // 3: UserGetAppConfigV3Resp
(*UserGetAgentsV3Req)(nil), // 4: UserGetAgentsV3Req
(*AgentVariableItem)(nil), // 5: AgentVariableItem
(*AgentMcpBindingItem)(nil), // 6: AgentMcpBindingItem
(*AgentItemV3)(nil), // 7: AgentItemV3
(*UserGetAgentsV3Resp)(nil), // 8: UserGetAgentsV3Resp
nil, // 9: UserGetAppConfigV3Resp.EnvEntry
(*DBProduct)(nil), // 10: DBProduct
(*ThirdSvcItem)(nil), // 11: ThirdSvcItem
(*AgentVoiceItem)(nil), // 12: AgentVoiceItem
(*CallTranslateRuleItem)(nil), // 4: CallTranslateRuleItem
(*CallTranslateItem)(nil), // 5: CallTranslateItem
(*UserGetAgentsV3Req)(nil), // 6: UserGetAgentsV3Req
(*AgentVariableItem)(nil), // 7: AgentVariableItem
(*AgentMcpBindingItem)(nil), // 8: AgentMcpBindingItem
(*AgentItemV3)(nil), // 9: AgentItemV3
(*UserGetAgentsV3Resp)(nil), // 10: UserGetAgentsV3Resp
nil, // 11: UserGetAppConfigV3Resp.EnvEntry
(*DBProduct)(nil), // 12: DBProduct
(*ThirdSvcItem)(nil), // 13: ThirdSvcItem
(*AgentVoiceItem)(nil), // 14: AgentVoiceItem
}
var file_user_user_msg_v3_proto_depIdxs = []int32{
1, // 0: MeetCapabilitiesItem.summary_services:type_name -> MeetSummaryServiceItem
9, // 1: UserGetAppConfigV3Resp.env:type_name -> UserGetAppConfigV3Resp.EnvEntry
7, // 2: UserGetAppConfigV3Resp.agents:type_name -> AgentItemV3
10, // 3: UserGetAppConfigV3Resp.products:type_name -> DBProduct
11, // 4: UserGetAppConfigV3Resp.thirdsvcs:type_name -> ThirdSvcItem
11, // 1: UserGetAppConfigV3Resp.env:type_name -> UserGetAppConfigV3Resp.EnvEntry
9, // 2: UserGetAppConfigV3Resp.agents:type_name -> AgentItemV3
12, // 3: UserGetAppConfigV3Resp.products:type_name -> DBProduct
13, // 4: UserGetAppConfigV3Resp.thirdsvcs:type_name -> ThirdSvcItem
2, // 5: UserGetAppConfigV3Resp.meet:type_name -> MeetCapabilitiesItem
12, // 6: AgentItemV3.voice_map:type_name -> AgentVoiceItem
5, // 7: AgentItemV3.variables:type_name -> AgentVariableItem
6, // 8: AgentItemV3.mcps:type_name -> AgentMcpBindingItem
7, // 9: UserGetAgentsV3Resp.agents:type_name -> AgentItemV3
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
5, // 6: UserGetAppConfigV3Resp.call_translate:type_name -> CallTranslateItem
4, // 7: CallTranslateItem.rules:type_name -> CallTranslateRuleItem
9, // 8: CallTranslateItem.agents:type_name -> AgentItemV3
14, // 9: AgentItemV3.voice_map:type_name -> AgentVoiceItem
7, // 10: AgentItemV3.variables:type_name -> AgentVariableItem
8, // 11: AgentItemV3.mcps:type_name -> AgentMcpBindingItem
9, // 12: UserGetAgentsV3Resp.agents:type_name -> AgentItemV3
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_user_user_msg_v3_proto_init() }
@ -809,7 +1008,7 @@ func file_user_user_msg_v3_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_user_msg_v3_proto_rawDesc), len(file_user_user_msg_v3_proto_rawDesc)),
NumEnums: 0,
NumMessages: 10,
NumMessages: 12,
NumExtensions: 0,
NumServices: 0,
},

120
apps/services/swagger/docs.go

@ -1761,6 +1761,57 @@ const docTemplate = `{
}
}
},
"/api/home/pay_getgoodssv2": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取商品列表(读管理后台「应用商品配置」,按应用隔离)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Pay"
],
"summary": "获取商品列表 v2",
"parameters": [
{
"description": "获取商品列表 v2",
"name": "order",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/pb.PayGetGoodssV2Req"
}
}
],
"responses": {
"200": {
"description": "商品列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/comm.HttpResult"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/pb.PayGetGoodssV2Resp"
}
}
}
]
}
}
}
}
},
"/api/home/pay_getorderlist": {
"post": {
"security": [
@ -1851,6 +1902,45 @@ const docTemplate = `{
}
}
},
"/api/home/pay_paypalnotifyv2": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "接收由HTTP回调网关转发来的支付通知,验签后按管理后台的商品配置发货",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Pay"
],
"summary": "处理PayPal支付异步通知 v2 [内部RPC]",
"parameters": [
{
"description": "PayPal 异步通知参数",
"name": "notify",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/pb.PayPayPalNotifyReq"
}
}
],
"responses": {
"200": {
"description": "处理结果",
"schema": {
"$ref": "#/definitions/comm.HttpResult"
}
}
}
}
},
"/api/home/pay_verifyorder": {
"post": {
"security": [
@ -5180,6 +5270,10 @@ const docTemplate = `{
"pb.DBProduct": {
"type": "object",
"properties": {
"appnames": {
"description": "产品归属的应用(应用中心 app_product.name,如 Voitrans,Lumi),CSV 可多选、空=未绑定。\n品牌商/渠道商账号的「应用」下拉即由本字段推导:只列出自己品牌名下产品绑定过的应用。",
"type": "string"
},
"bassfilter": {
"description": "@go_tags(` + "`" + `gorm:\"bassfilter\" json:\"bassfilter\"` + "`" + `) 过滤低音音频参数(0-1000)",
"type": "integer"
@ -5216,6 +5310,14 @@ const docTemplate = `{
"description": "@go_tags(` + "`" + `gorm:\"channelextra\" json:\"channelextra\"` + "`" + `) 渠道附加数据(JSON字符串)",
"type": "string"
},
"createtime": {
"description": "@go_tags(` + "`" + `gorm:\"createtime\" json:\"createtime\"` + "`" + `) 创建时间(秒);历史产品为0",
"type": "integer"
},
"description": {
"description": "产品说明:这款产品是什么、有什么特性,给后台运营与客户看的,与版本更新说明(updatedescription)无关。",
"type": "string"
},
"devicecodecount": {
"description": "@go_tags(` + "`" + `gorm:\"devicecodecount\" json:\"devicecodecount\"` + "`" + `) 每副产品生产的设备码数量(0或1=单份,2=双份)",
"type": "integer"
@ -5276,6 +5378,10 @@ const docTemplate = `{
"description": "@go_tags(` + "`" + `gorm:\"productimage\" json:\"productimage\"` + "`" + `) 产品图片",
"type": "string"
},
"providerid": {
"description": "产品用的是哪套芯片方案(DBSolutionProvider.id)。生产设备码时的服务ID(CID)直接取这里——\n以前从品牌商推导,但同一品牌的不同产品可能用不同方案商的方案,推导会串号,故改为产品级绑定,新建产品必填。",
"type": "integer"
},
"scanuuid": {
"description": "@go_tags(` + "`" + `gorm:\"scanuuid\" json:\"scanuuid\"` + "`" + `) 设备扫描唯一id",
"type": "string"
@ -6987,6 +7093,20 @@ const docTemplate = `{
}
}
},
"pb.PayGetGoodssV2Req": {
"type": "object"
},
"pb.PayGetGoodssV2Resp": {
"type": "object",
"properties": {
"goodss": {
"type": "array",
"items": {
"$ref": "#/definitions/pb.DBGoods"
}
}
}
},
"pb.PayGetOrderListReq": {
"type": "object",
"properties": {

120
apps/services/swagger/swagger.json

@ -1753,6 +1753,57 @@
}
}
},
"/api/home/pay_getgoodssv2": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "获取商品列表(读管理后台「应用商品配置」,按应用隔离)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Pay"
],
"summary": "获取商品列表 v2",
"parameters": [
{
"description": "获取商品列表 v2",
"name": "order",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/pb.PayGetGoodssV2Req"
}
}
],
"responses": {
"200": {
"description": "商品列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/comm.HttpResult"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/pb.PayGetGoodssV2Resp"
}
}
}
]
}
}
}
}
},
"/api/home/pay_getorderlist": {
"post": {
"security": [
@ -1843,6 +1894,45 @@
}
}
},
"/api/home/pay_paypalnotifyv2": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "接收由HTTP回调网关转发来的支付通知,验签后按管理后台的商品配置发货",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Pay"
],
"summary": "处理PayPal支付异步通知 v2 [内部RPC]",
"parameters": [
{
"description": "PayPal 异步通知参数",
"name": "notify",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/pb.PayPayPalNotifyReq"
}
}
],
"responses": {
"200": {
"description": "处理结果",
"schema": {
"$ref": "#/definitions/comm.HttpResult"
}
}
}
}
},
"/api/home/pay_verifyorder": {
"post": {
"security": [
@ -5172,6 +5262,10 @@
"pb.DBProduct": {
"type": "object",
"properties": {
"appnames": {
"description": "产品归属的应用(应用中心 app_product.name,如 Voitrans,Lumi),CSV 可多选、空=未绑定。\n品牌商/渠道商账号的「应用」下拉即由本字段推导:只列出自己品牌名下产品绑定过的应用。",
"type": "string"
},
"bassfilter": {
"description": "@go_tags(`gorm:\"bassfilter\" json:\"bassfilter\"`) 过滤低音音频参数(0-1000)",
"type": "integer"
@ -5208,6 +5302,14 @@
"description": "@go_tags(`gorm:\"channelextra\" json:\"channelextra\"`) 渠道附加数据(JSON字符串)",
"type": "string"
},
"createtime": {
"description": "@go_tags(`gorm:\"createtime\" json:\"createtime\"`) 创建时间(秒);历史产品为0",
"type": "integer"
},
"description": {
"description": "产品说明:这款产品是什么、有什么特性,给后台运营与客户看的,与版本更新说明(updatedescription)无关。",
"type": "string"
},
"devicecodecount": {
"description": "@go_tags(`gorm:\"devicecodecount\" json:\"devicecodecount\"`) 每副产品生产的设备码数量(0或1=单份,2=双份)",
"type": "integer"
@ -5268,6 +5370,10 @@
"description": "@go_tags(`gorm:\"productimage\" json:\"productimage\"`) 产品图片",
"type": "string"
},
"providerid": {
"description": "产品用的是哪套芯片方案(DBSolutionProvider.id)。生产设备码时的服务ID(CID)直接取这里——\n以前从品牌商推导,但同一品牌的不同产品可能用不同方案商的方案,推导会串号,故改为产品级绑定,新建产品必填。",
"type": "integer"
},
"scanuuid": {
"description": "@go_tags(`gorm:\"scanuuid\" json:\"scanuuid\"`) 设备扫描唯一id",
"type": "string"
@ -6979,6 +7085,20 @@
}
}
},
"pb.PayGetGoodssV2Req": {
"type": "object"
},
"pb.PayGetGoodssV2Resp": {
"type": "object",
"properties": {
"goodss": {
"type": "array",
"items": {
"$ref": "#/definitions/pb.DBGoods"
}
}
}
},
"pb.PayGetOrderListReq": {
"type": "object",
"properties": {

88
apps/services/swagger/swagger.yaml

@ -188,8 +188,8 @@ definitions:
type: string
type: array
mcps:
description: '@go_tags(`json:"mcps"`) MCP 绑定(含每个服务允许的工具);mcp_svc_ids 是它的
id 平铺,保留给老客户端'
description: '@go_tags(`json:"mcps"`) MCP 绑定(含每个服务允许的工具);mcp_svc_ids 是它的 id
平铺,保留给老客户端'
items:
$ref: '#/definitions/pb.AgentMcpBindingItem'
type: array
@ -235,8 +235,7 @@ definitions:
里)'
type: string
tools:
description: '@go_tags(`json:"tools"`) 允许调用的工具名;空=该服务的工具全放开(仍受服务自身
tools 白名单约束)'
description: '@go_tags(`json:"tools"`) 允许调用的工具名;空=该服务的工具全放开(仍受服务自身 tools 白名单约束)'
items:
type: string
type: array
@ -1260,6 +1259,11 @@ definitions:
type: object
pb.DBProduct:
properties:
appnames:
description: |-
产品归属的应用(应用中心 app_product.name,如 Voitrans,Lumi),CSV 可多选、空=未绑定。
品牌商/渠道商账号的「应用」下拉即由本字段推导:只列出自己品牌名下产品绑定过的应用。
type: string
bassfilter:
description: '@go_tags(`gorm:"bassfilter" json:"bassfilter"`) 过滤低音音频参数(0-1000)'
type: integer
@ -1291,6 +1295,12 @@ definitions:
channelextra:
description: '@go_tags(`gorm:"channelextra" json:"channelextra"`) 渠道附加数据(JSON字符串)'
type: string
createtime:
description: '@go_tags(`gorm:"createtime" json:"createtime"`) 创建时间(秒);历史产品为0'
type: integer
description:
description: 产品说明:这款产品是什么、有什么特性,给后台运营与客户看的,与版本更新说明(updatedescription)无关。
type: string
devicecodecount:
description: '@go_tags(`gorm:"devicecodecount" json:"devicecodecount"`) 每副产品生产的设备码数量(0或1=单份,2=双份)'
type: integer
@ -1341,6 +1351,11 @@ definitions:
productimage:
description: '@go_tags(`gorm:"productimage" json:"productimage"`) 产品图片'
type: string
providerid:
description: |-
产品用的是哪套芯片方案(DBSolutionProvider.id)。生产设备码时的服务ID(CID)直接取这里——
以前从品牌商推导,但同一品牌的不同产品可能用不同方案商的方案,推导会串号,故改为产品级绑定,新建产品必填。
type: integer
scanuuid:
description: '@go_tags(`gorm:"scanuuid" json:"scanuuid"`) 设备扫描唯一id'
type: string
@ -2609,6 +2624,15 @@ definitions:
$ref: '#/definitions/pb.DBGoods'
type: array
type: object
pb.PayGetGoodssV2Req:
type: object
pb.PayGetGoodssV2Resp:
properties:
goodss:
items:
$ref: '#/definitions/pb.DBGoods'
type: array
type: object
pb.PayGetOrderListReq:
properties:
page:
@ -3074,7 +3098,8 @@ definitions:
pb.UserGetAppConfigV3Resp:
properties:
agents:
description: '@go_tags(`json:"agents"`) 智能体列表(结构同 user_getagents_v3,含 type 与服务编排)'
description: '@go_tags(`json:"agents"`) 智能体列表(结构同 user_getagents_v3,含 type
与服务编排)'
items:
$ref: '#/definitions/pb.AgentItemV3'
type: array
@ -4546,6 +4571,35 @@ paths:
summary: 获取商品列表
tags:
- Pay
/api/home/pay_getgoodssv2:
post:
consumes:
- application/json
description: 获取商品列表(读管理后台「应用商品配置」,按应用隔离)
parameters:
- description: 获取商品列表 v2
in: body
name: order
required: true
schema:
$ref: '#/definitions/pb.PayGetGoodssV2Req'
produces:
- application/json
responses:
"200":
description: 商品列表
schema:
allOf:
- $ref: '#/definitions/comm.HttpResult'
- properties:
data:
$ref: '#/definitions/pb.PayGetGoodssV2Resp'
type: object
security:
- BearerAuth: []
summary: 获取商品列表 v2
tags:
- Pay
/api/home/pay_getorderlist:
post:
consumes:
@ -4599,6 +4653,30 @@ paths:
summary: 处理PayPal支付异步通知 [内部RPC]
tags:
- Pay
/api/home/pay_paypalnotifyv2:
post:
consumes:
- application/json
description: 接收由HTTP回调网关转发来的支付通知,验签后按管理后台的商品配置发货
parameters:
- description: PayPal 异步通知参数
in: body
name: notify
required: true
schema:
$ref: '#/definitions/pb.PayPayPalNotifyReq'
produces:
- application/json
responses:
"200":
description: 处理结果
schema:
$ref: '#/definitions/comm.HttpResult'
security:
- BearerAuth: []
summary: 处理PayPal支付异步通知 v2 [内部RPC]
tags:
- Pay
/api/home/pay_verifyorder:
post:
consumes:

Loading…
Cancel
Save