0.11.7: 代码审查查缺补漏(安全/并发/前端三轮审查修复)
安全: - 渠道 webhook 入站强制令牌鉴权(恒定时间比较+统一文案),企微签名官方算法; - 报表/概览/systemInfo 端点按 usage:read/audit:read/system:manage 授权; - sso_error 固定错误码;个人渠道令牌仅请求头;工具出站 Dialer.Control 消除 DNS rebinding TOCTOU;新增 channel:read/manage 权限;限流倍数上限 10。 并发/一致性: - 任务上报单条条件 UPDATE 防重放双提交;认领回收过期 claimed 任务; - 审批改先开通后落记录(幂等,无嵌套事务);聊天消息单事务落库; - 会话列表校验 AuthVersion;吊销先 Del 后 SRem;删工具保护调用历史; - rejected 冷却 24h;限流被拒补偿;maintenance 清理限流窗口。 前端/菜单: - 修复 gatewayChildren late-append 导致 reports/tenants/channels 菜单不可见; - 聊天改名 PUT 对齐;渠道编辑清空凭据防串写+启用开关; - 聊天响应防串扰;报表本地时区日期。
This commit is contained in:
@@ -75,7 +75,10 @@
|
||||
<ElInput v-model="form.api_key" type="password" show-password :placeholder="editingId ? '留空不更换' : '必填'" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="入站令牌">
|
||||
<ElInput v-model="form.inbound_token" placeholder="webhook/企微回调鉴权令牌" />
|
||||
<ElInput v-model="form.inbound_token" type="password" show-password placeholder="webhook/企微回调鉴权令牌" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="启用">
|
||||
<ElSwitch v-model="form.enabled" />
|
||||
</ElFormItem>
|
||||
<template v-if="form.kind === 'wecom'">
|
||||
<ElFormItem label="CorpID"><ElInput v-model="form.corp_id" /></ElFormItem>
|
||||
@@ -155,7 +158,8 @@
|
||||
const form = reactive({
|
||||
code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '',
|
||||
inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '',
|
||||
department_ids: [] as string[]
|
||||
department_ids: [] as string[],
|
||||
enabled: true
|
||||
})
|
||||
|
||||
async function load() {
|
||||
@@ -176,7 +180,7 @@
|
||||
|
||||
function openCreate() {
|
||||
editingId.value = ''
|
||||
Object.assign(form, { code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '', inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '', department_ids: [] })
|
||||
Object.assign(form, { code: '', name: '', kind: 'webhook', binding_provider: '', binding_model: '', api_key: '', inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '', department_ids: [], enabled: true })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
@@ -184,8 +188,12 @@
|
||||
editingId.value = row.id
|
||||
Object.assign(form, {
|
||||
code: row.code, name: row.name, kind: row.kind, api_key: '',
|
||||
// 显式清空全部平台配置字段:否则上一条渠道的 Secret/Token 会残留并
|
||||
// 在保存时静默覆盖当前渠道的凭据。
|
||||
inbound_token: '', corp_id: '', secret: '', agent_id: '', ding_robot_token: '', feishu_app_id: '', feishu_app_secret: '',
|
||||
binding_provider: row.model_binding?.provider || '', binding_model: row.model_binding?.model || '',
|
||||
department_ids: [...(row.department_ids || [])]
|
||||
department_ids: [...(row.department_ids || [])],
|
||||
enabled: row.enabled
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
@@ -213,7 +221,8 @@
|
||||
code: form.code, name: form.name, kind: form.kind, config,
|
||||
model_binding: { provider: form.binding_provider || undefined, model: form.binding_model || undefined },
|
||||
department_ids: form.department_ids,
|
||||
api_key: form.api_key
|
||||
api_key: form.api_key,
|
||||
enabled: form.enabled
|
||||
}
|
||||
if (editingId.value) {
|
||||
await request.put({ url: `/api/v1/admin/channels/${editingId.value}`, params: payload })
|
||||
|
||||
@@ -104,13 +104,17 @@
|
||||
const range = ref<[string, string]>([daysAgo(6), today()])
|
||||
const dailyUsage = ref<any[]>([])
|
||||
|
||||
function fmtLocal(d: Date) {
|
||||
const pad = (v: number) => String(v).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
}
|
||||
function daysAgo(n: number) {
|
||||
const d = new Date()
|
||||
d.setDate(d.getDate() - n)
|
||||
return d.toISOString().slice(0, 10)
|
||||
return fmtLocal(d)
|
||||
}
|
||||
function today() {
|
||||
return new Date().toISOString().slice(0, 10)
|
||||
return fmtLocal(new Date())
|
||||
}
|
||||
function costText(value: number | null | undefined) {
|
||||
return value != null && value > 0 ? `USD ${(value / 1e6).toFixed(4)}` : '—'
|
||||
|
||||
@@ -209,7 +209,13 @@
|
||||
history.replaceState(null, '', window.location.pathname + window.location.hash)
|
||||
await router.replace('/')
|
||||
} else if (ssoError) {
|
||||
ElMessage.error(ssoError)
|
||||
const errorText = ({
|
||||
unbound: '该企业账号尚未绑定本系统账号,请先用账号密码登录后在「账号安全」中绑定',
|
||||
disabled: '账号已被停用',
|
||||
expired: '登录状态已过期,请重新扫码',
|
||||
login_failed: '企业登录失败,请重试或联系管理员'
|
||||
} as Record<string, string>)[ssoError] || ssoError
|
||||
ElMessage.error(errorText)
|
||||
history.replaceState(null, '', window.location.pathname + window.location.hash)
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -173,7 +173,10 @@ async function send() {
|
||||
draft.value = ''
|
||||
scrollToBottom()
|
||||
try {
|
||||
// 响应到达时校验会话未切换:发送中切模型/切会话时丢弃迟到响应,防止
|
||||
// 串入新会话。
|
||||
const response = await appendChatMessage(id, text)
|
||||
if (currentId.value !== id) return
|
||||
const choices = (response.choices as Array<{ message?: { content?: string } }>) || []
|
||||
const answer = choices[0]?.message?.content || ''
|
||||
messages.value.push({ sequence: messages.value.length + 1, role: 'assistant', content: answer, created_at: '' })
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<div class="text-sm">个人限流倍数</div>
|
||||
<p class="text-g-500 mt-1 text-sm">你的个人调用按此倍数放宽工具限流(例如工具限流 10 rpm、倍数 2 时个人可调用 20 rpm)</p>
|
||||
</div>
|
||||
<ElInputNumber v-model="policy.rate_limit_multiplier" :min="1" :max="100" class="w-32" :disabled="savingPolicy" @change="savePolicy" />
|
||||
<ElInputNumber v-model="policy.rate_limit_multiplier" :min="1" :max="10" class="w-32" :disabled="savingPolicy" @change="savePolicy" />
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
Reference in New Issue
Block a user