0.11.2: 旗舰版第三轮完善(通用聊天/企微钉钉飞书扫码登录/个人安全策略)
- 门户通用聊天:选择已批准模型直接对话,审批通过后自动开通用户级运行时 API Key(加密落库,限额取批准值),聊天经受管网关统一认证/限流/配额/审计; 会话哈希链完整性 + busy 租约防并发,失败不落库。 - 扫码登录:identity_providers 扩展 wecom/dingtalk/feishu,管理端配置 (AppID/AppSecret/AgentID/回调/自动开户/默认部门),登录页自动展示; one-time state 防 CSRF,provider_uid 全局唯一防多账号绑定,平台端点 固定公网 URL 复用 public-only 拨号。 - 个人安全策略:账号安全页(登录设备管理/吊销非当前会话/登录提醒开关/ 扫码绑定解绑),登录成功发布 security.login_detected 事件按偏好落站内信 (新增 security 类别),会话索引只存令牌摘要并惰性清理。 - 迁移 000038-000041;修复 social update 参数越界/凭据回读/路由挂载缺失; 全量测试 25 包通过,前端 admin/portal 构建通过,端到端验证完成。
This commit is contained in:
@@ -45,6 +45,15 @@ func NewHTTPHandler(service *Service, identityService *identity.Service) *HTTPHa
|
||||
h.mux.HandleFunc("DELETE /api/v1/portal/apps/{code}/conversations/{id}", h.deleteConversation)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/apps/{code}/conversations/{id}", h.getConversation)
|
||||
h.mux.HandleFunc("POST /api/v1/portal/apps/{code}/conversations/{id}/messages", h.appendConversationMessage)
|
||||
// 通用聊天:选择已批准模型直接对话(复用用户运行时凭据)。
|
||||
h.mux.HandleFunc("GET /api/v1/portal/chat/models", h.chatModels)
|
||||
h.mux.HandleFunc("POST /api/v1/portal/chat/completions", h.chatOnce)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/chat/sessions", h.listChatSessions)
|
||||
h.mux.HandleFunc("POST /api/v1/portal/chat/sessions", h.createChatSession)
|
||||
h.mux.HandleFunc("PATCH /api/v1/portal/chat/sessions/{id}", h.renameChatSession)
|
||||
h.mux.HandleFunc("DELETE /api/v1/portal/chat/sessions/{id}", h.deleteChatSession)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/chat/sessions/{id}", h.getChatSession)
|
||||
h.mux.HandleFunc("POST /api/v1/portal/chat/sessions/{id}/messages", h.appendChatMessage)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/marketplace", h.marketplace)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/marketplace/categories", h.marketplaceCategories)
|
||||
h.mux.HandleFunc("GET /api/v1/portal/marketplace/installed", h.marketplaceInstalled)
|
||||
@@ -589,3 +598,135 @@ func (h *HTTPHandler) appendConversationMessage(w http.ResponseWriter, r *http.R
|
||||
}
|
||||
writeApplicationResponse(w, response)
|
||||
}
|
||||
|
||||
// --- 通用聊天 ---
|
||||
|
||||
type chatCompletionsInput struct {
|
||||
ProviderCode string `json:"provider_code"`
|
||||
Model string `json:"model"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) chatModels(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := h.service.ChatModels(r.Context(), a)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, items)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) chatOnce(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input chatCompletionsInput
|
||||
if !decode(w, r, &input) {
|
||||
return
|
||||
}
|
||||
response, err := h.service.ChatOnce(r.Context(), a, input.ProviderCode, input.Model, input.Message)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
writeApplicationResponse(w, response)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) listChatSessions(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
items, err := h.service.ListChatSessions(r.Context(), a, limit)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, items)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) createChatSession(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input chatCompletionsInput
|
||||
if !decode(w, r, &input) {
|
||||
return
|
||||
}
|
||||
item, err := h.service.CreateChatSession(r.Context(), a, input.ProviderCode, input.Model)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, item)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) renameChatSession(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Title string `json:"title"`
|
||||
}
|
||||
if !decode(w, r, &input) {
|
||||
return
|
||||
}
|
||||
item, err := h.service.RenameChatSession(r.Context(), a, r.PathValue("id"), input.Title)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, item)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) deleteChatSession(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.service.DeleteChatSession(r.Context(), a, r.PathValue("id")); err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) getChatSession(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
item, err := h.service.ChatSession(r.Context(), a, r.PathValue("id"))
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
apiresponse.OK(w, item)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) appendChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := h.account(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if !decode(w, r, &input) {
|
||||
return
|
||||
}
|
||||
response, err := h.service.AppendChatMessage(r.Context(), a, r.PathValue("id"), input.Message)
|
||||
if err != nil {
|
||||
portalError(w, err)
|
||||
return
|
||||
}
|
||||
writeApplicationResponse(w, response)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user