前言
本文將深入分析Coze Studio項目中用戶登錄后點擊"項目開發"功能的后端實現,通過源碼解讀來理解整個智能體項目管理系統的架構設計和技術實現。
項目架構概覽
整體架構設計
Coze Studio后端采用了經典的分層架構模式,將項目開發功能劃分為以下幾個核心層次:
┌─────────────────────────────────────────────────────────────┐
│ IDL接口定義層 │
│ ┌─────────────┐ ┌───────────────── ┐ ┌─────────────┐ │
│ │ base.thrift │ │openapiauth.thrift│ │ api.thrift │ │
│ └─────────────┘ └───────────────── ┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ API網關層 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Handler │ │ Router │ │ Middleware │ │
│ │ 處理器 │ │ 路由 │ │ 中間件 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ 應用服務層 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SearchApplicationService │ │
│ │ GetDraftIntelligenceList │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ 領域服務層 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ APP Service Search Service │ │
│ │ SingleAgent Service │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ 數據訪問層 │
│ ┌─ ─ ─── ─── ── ─ ─ ─┐ │
│ │ APPDraftDAO │ │
│ │ SingleAgentDraftDAO│ │
│ └── ─ ── ─── ── ── ─ ┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ 基礎設施層 │
│ ┌─ ─ ─── ─── ── ─ ─ ─┐ │
│ │ gorm.DB │ │
│ │ es.Client │ │
│ └── ─ ── ─── ── ── ─ ┘ │
└─────────────────────────────────────────────────────────────┘↓
┌─────────────────────────────────────────────────────────────┐
│ 存儲服務層 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MySQL數據庫 │ │
│ │ ElasticSearch數據庫 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
1. IDL接口定義層
IDL基礎類型定義(base.thrift)
文件位置:idl/base.thrift
核心代碼:
namespace py base
namespace go base
namespace java com.bytedance.thrift.basestruct TrafficEnv {1: bool Open = false,2: string Env = "" ,
}struct Base {1: string LogID = "",2: string Caller = "",3: string Addr = "",4: string Client = "",5: optional TrafficEnv TrafficEnv ,6: optional map<string,string> Extra ,
}struct BaseResp {1: string StatusMessage = "",2: i32 StatusCode = 0 ,3: optional map<string,string> Extra ,
}
文件作用:
定義了項目中所有接口的基礎數據結構,作為其他IDL文件的依賴基礎。
項目開發查詢接口定義(intelligence.thrift)
文件位置:idl/app/intelligence.thrift
GetDraftIntelligenceList接口定義
search.GetDraftIntelligenceListResponse GetDraftIntelligenceList(1: search.GetDraftIntelligenceListRequest req)
(api.post='/api/intelligence_api/search/get_draft_intelligence_list', api.category="search",agw.preserve_base="true")
項目開發查詢結構體定義(search.thrift)
文件位置:idl/app/search.thrift
請求結構體:
struct GetDraftIntelligenceListRequest {1: required i64 space_id (agw.js_conv="str", api.js_conv="true"),2: optional string name,3: optional bool has_published,4: optional list<intelligence_common_struct.IntelligenceStatus> status,5: optional list<intelligence_common_struct.IntelligenceType> types,6: optional SearchScope search_scope,51: optional bool is_fav,52: optional bool recently_open,99: optional GetDraftIntelligenceListOption option,100: optional OrderBy order_by,101: optional string cursor_id,102: optional i32 size,255: optional base.Base Base
}
響應結構體:
struct IntelligenceData {1: intelligence_common_struct.IntelligenceBasicInfo basic_info,2: intelligence_common_struct.IntelligenceType type,3: IntelligencePublishInfo publish_info,4: IntelligencePermissionInfo permission_info,5: common_struct.User owner_info,6: common_struct.AuditInfo latest_audit_info,7: FavoriteInfo favorite_info,50: OtherInfo other_info,
}struct DraftIntelligenceListData {1: list<IntelligenceData> intelligences,2: i32 total,3: bool has_more,4: string next_cursor_id,
}struct GetDraftIntelligenceListResponse {1: DraftIntelligenceListData data,253: i32 code,254: string msg,255: optional base.BaseResp BaseResp (api.none="true"),
}
項目開發查詢公共結構體定義(common_struct.thrift)
文件位置:idl/app/common_struct/common_struct.thrift
公共結構體:
namespace go app.intelligence.commonstruct UserLabel {1: string label_id ,2: string label_name ,3: string icon_uri ,4: string icon_url ,5: string jump_link ,
}struct User {1: i64 user_id (agw.js_conv="str", api.js_conv="true"),2: string nickname, // user nickname3: string avatar_url, // user avatar4: string user_unique_name, // user name5: UserLabel user_label, // user tag
}struct IntelligencePublishInfo {1: string publish_time,2: bool has_published,3: list<ConnectorInfo> connectors,
}
文件位置:idl/app/common_struct/intelligence_common_struct.thrift
公共結構體:
namespace go app.intelligence.commonenum IntelligenceStatus {Using = 1,Deleted = 2,Banned = 3,MoveFailed = 4, // Migration failedCopying = 5, // CopyingCopyFailed = 6, // Copy failed
}enum IntelligenceType {Bot = 1Project = 2
}struct IntelligenceBasicInfo {1: i64 id (agw.js_conv="str", api.js_conv="true"),2: string name,3: string description,4: string icon_uri,5: string icon_url,6: i64 space_id (agw.js_conv="str", api.js_conv="true"),7: i64 owner_id (agw.js_conv="str", api.js_conv="true"),8: i64 create_time (agw.js_conv="str", api.js_conv="true"),9: i64 update_time (agw.js_conv="str", api.js_conv="true"),10: IntelligenceStatus status,11: i64 publish_time (agw.js_conv="str", api.js_conv="true"),12: optional string enterprise_id,13: optional i64 organization_id,
}
IDL主API服務聚合文件(api.thrift)
文件位置:idl/api.thrift
該文件是整個Coze項目的API服務聚合入口點,負責將所有業務模塊的IDL服務定義統一聚合,為代碼生成工具提供完整的服務接口定義。
核心代碼:
include "./app/intelligence.thrift"namespace go coze// 項目開發核心服務聚合
service IntelligenceService extends intelligence.IntelligenceService {}
// 其他業務服務聚合
項目開發接口聚合說明:
通過 service IntelligenceService extends intelligence.IntelligenceService {}
聚合定義,api.thrift將intelligence.thrift中定義的所有項目開發相關接口統一暴露,包括:
文件作用:
- 服務聚合中心: 統一管理所有業務模塊的服務接口定義
- 代碼生成入口: 作為Hertz框架代碼生成的主要入口文件
- 接口統一暴露: 將分散在各個模塊的接口統一暴露給客戶端
- 依賴管理: 通過include語句管理各模塊間的依賴關系
- 命名空間管理: 統一設置Go語言的包命名空間
技術特性:
- 使用Apache Thrift作為IDL(接口定義語言)
- 支持服務繼承和擴展機制
- 模塊化的服務組織結構
- 統一的命名空間管理
- 自動代碼生成支持
- 跨語言兼容性
- 強類型約束和接口安全性
2. API網關層
接口定義-intelligence.go文件詳細分析
文件位置:backend\api\model\app\intelligence\intelligence.go
IntelligenceService接口定義
// IntelligenceService 智能體服務接口
type IntelligenceService interface {// GetDraftIntelligenceInfo 獲取草稿智能體信息// 用于獲取指定智能體項目的詳細信息,支持版本預覽GetDraftIntelligenceList(ctx context.Context, req *GetDraftIntelligenceListRequest) (r *GetDraftIntelligenceListResponse, err error)// 其他接口方法...}
請求響應結構體定義
文件位置:backend\api\model\app\intelligence\search.go
GetDraftIntelligenceListRequest 請求結構體:
type GetDraftIntelligenceListRequest struct {SpaceID int64 `thrift:"space_id,1,required" form:"space_id,required" json:"space_id,string,required" query:"space_id,required"`Name *string `thrift:"name,2,optional" form:"name" json:"name,omitempty" query:"name"`HasPublished *bool `thrift:"has_published,3,optional" form:"has_published" json:"has_published,omitempty" query:"has_published"`Status []common.IntelligenceStatus `thrift:"status,4,optional" form:"status" json:"status,omitempty" query:"status"`Types []common.IntelligenceType `thrift:"types,5,optional" form:"types" json:"types,omitempty" query:"types"`SearchScope *SearchScope `thrift:"search_scope,6,optional" form:"search_scope" json:"search_scope,omitempty" query:"search_scope"`IsFav *bool `thrift:"is_fav,51,optional" form:"is_fav" json:"is_fav,omitempty" query:"is_fav"`RecentlyOpen *bool `thrift:"recently_open,52,optional" form:"recently_open" json:"recently_open,omitempty" query:"recently_open"`Option *GetDraftIntelligenceListOption `thrift:"option,99,optional" form:"option" json:"option,omitempty" query:"option"`OrderBy *OrderBy `thrift:"order_by,100,optional" form:"order_by" json:"order_by,omitempty" query:"order_by"`CursorID *string `thrift:"cursor_id,101,optional" form:"cursor_id" json:"cursor_id,omitempty" query:"cursor_id"`Size *int32 `thrift:"size,102,optional" form:"size" json:"size,omitempty" query:"size"`Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"`
}
GetDraftIntelligenceInfoResponse 響應結構體:
type GetDraftIntelligenceListResponse struct {Data *DraftIntelligenceListData `thrift:"data,1" form:"data" json:"data" query:"data"`Code int32 `thrift:"code,253" form:"code" json:"code" query:"code"`Msg string `thrift:"msg,254" form:"msg" json:"msg" query:"msg"`BaseResp *base.BaseResp `thrift:"BaseResp,255,optional" form:"-" json:"-" query:"-"`
}
接口功能說明
業務功能:
- 智能體詳情獲取:根據智能體ID獲取完整的項目信息
- 版本預覽支持:通過可選的version參數支持特定版本的預覽
- 多維度信息:返回基本信息、發布狀態、所有者信息等完整數據
- 權限控制:基于用戶身份和項目權限進行訪問控制
技術特性:
- 類型安全:使用強類型定義確保數據一致性
- 多格式支持:支持thrift、form、json、query等多種序列化格式
- 可選字段:使用optional標記支持向后兼容
- 統一響應:遵循統一的響應格式規范
文件作用:
由thriftgo自動生成的Go代碼文件,基于IDL定義生成對應的Go結構體和接口,提供類型安全的API模型。該文件實現了完整的Thrift RPC通信機制,包括客戶端調用、服務端處理、序列化/反序列化等功能,確保了分布式服務間的可靠通信。
項目開發接口處理器實現
文件位置:backend/api/handler/coze/intelligence_service.go
該文件包含了用戶登錄后點擊項目開發功能的所有核心API接口處理器,主要負責處理草稿項目的CRUD操作、項目發布、項目復制等功能。
核心代碼:
// GetDraftIntelligenceList 獲取草稿智能體列表
// 用戶登錄后進入項目開發頁面時調用此接口獲取項目列表
// @router /api/intelligence/draft/list [GET]
func GetDraftIntelligenceList(ctx context.Context, c *app.RequestContext) {var err errorvar req intelligence.GetDraftIntelligenceListRequesterr = c.BindAndValidate(&req)if err != nil {invalidParamRequestResponse(c, err.Error())return}// 調用搜索服務獲取用戶的草稿項目列表resp, err := search.SearchSVC.GetDraftIntelligenceList(ctx, &req)if err != nil {logs.CtxErrorf(ctx, "SearchSVC.GetDraftIntelligenceList failed, err=%v", err)internalServerErrorResponse(ctx, c, err)return}c.JSON(consts.StatusOK, resp)
}
實現功能:
- 項目列表獲取:獲取用戶的草稿智能體列表,支持分頁和搜索
路由注冊實現-api.go文件詳細分析
文件位置:backend/api/router/coze/api.go
核心代碼:
// Code generated by hertz generator. DO NOT EDIT.func Register(r *server.Hertz) {root := r.Group("/", rootMw()...){_api := root.Group("/api", _apiMw()...){_intelligence_api := _api.Group("/intelligence_api", _intelligence_apiMw()...){_search := _intelligence_api.Group("/search", _searchMw()...)_search.POST("/get_draft_intelligence_list", append(_getdraftintelligencelistMw(), coze.GetDraftIntelligenceList)...)}}}
}
文件作用:
此文件是Coze Studio后端的核心路由注冊文件,由hertz generator自動生成,負責將所有HTTP API接口路由與對應的處理函數進行綁定和注冊。該文件構建了完整的RESTful API路由樹結構。對于智能體項目開發模塊,構建了層次化的路由結構:
/api/intelligence_api/search/get_draft_intelligence_list [POST]
├── _intelligence_apiMw() # 智能體API組中間件
├── _searchMw() # 搜索模塊中間件
├── _getdraftintelligencelistMw() # 草稿智能體列表接口中間件
└── coze.GetDraftIntelligenceList # 處理函數
中間件系統-middleware.go文件詳細分析
文件位置:backend/api/router/coze/middleware.go
核心代碼:
func _intelligence_apiMw() []app.HandlerFunc {// 智能體API模塊中間件return nil
}func _searchMw() []app.HandlerFunc {// 搜索模塊中間件return nil
}func _getdraftintelligencelistMw() []app.HandlerFunc {// 草稿智能體列表查詢接口專用中間件return nil
}
文件作用:
- 中間件函數定義:為智能體項目開發模塊的每個路由組和特定路由提供中間件掛載點
- 路由層級管理:按照路由的層級結構組織中間件函數,支持三層中間件架構
- 開發者擴展接口:提供統一的接口供開發者添加自定義中間件邏輯,如認證、鑒權、限流、日志記錄等
- 粒度化控制:支持從模塊級別到接口級別的細粒度中間件控制
API網關層Restful接口路由-Coze+Hertz
Hertz為每個HTTP方法維護獨立的路由樹,通過分組路由的方式構建層次化的API結構。對于草稿智能體列表查詢接口的完整路由鏈路:
/api/intelligence_api/search/get_draft_intelligence_list [POST]
├── rootMw() # 根級中間件
├── _apiMw() # API組中間件
├── _intelligence_apiMw() # 智能體API組中間件
├── _searchMw() # 搜索模塊中間件
├── _getdraftintelligencelistMw() # 接口級中間件
└── coze.GetDraftIntelligenceList # 處理函數
這種設計的優勢:
- 層次化管理:不同層級的中間件處理不同的關注點
- 可擴展性:每個層級都可以獨立添加中間件
- 性能優化:中間件按需執行,避免不必要的開銷
- POST請求支持:專門處理POST請求的JSON數據綁定和驗證
- 智能體項目管理:專門為智能體項目開發功能設計的路由結構
3. 應用服務層
SearchApplicationService初始化
文件位置:backend/application/search/resource_search.go
和 backend/application/search/init.go
SearchApplicationService是搜索應用服務層的核心組件,負責處理項目和資源的搜索、獲取、收藏等業務邏輯,是連接API層和領域層的重要橋梁。
服務結構定義
文件位置:backend/application/search/resource_search.go
// SearchApplicationService 搜索應用服務,處理項目和資源搜索的核心業務邏輯
var SearchSVC = &SearchApplicationService{}type SearchApplicationService struct {*ServiceComponents // 嵌入服務組件依賴DomainSVC search.Search // 搜索領域服務
}// 資源類型到默認圖標的映射
var resType2iconURI = map[common.ResType]string{common.ResType_Plugin: consts.DefaultPluginIcon,common.ResType_Workflow: consts.DefaultWorkflowIcon,common.ResType_Knowledge: consts.DefaultDatasetIcon,common.ResType_Prompt: consts.DefaultPromptIcon,common.ResType_Database: consts.DefaultDatabaseIcon,
}
服務組件依賴
文件位置:backend/application/search/init.go
// ServiceComponents 定義搜索服務所需的所有依賴組件
type ServiceComponents struct {DB *gorm.DB // 數據庫連接Cache cache.Cmdable // 緩存服務TOS storage.Storage // 對象存儲服務ESClient es.Client // Elasticsearch客戶端ProjectEventBus ProjectEventBus // 項目事件總線ResourceEventBus ResourceEventBus // 資源事件總線SingleAgentDomainSVC singleagent.SingleAgent // 單智能體領域服務APPDomainSVC app.AppService // APP領域服務KnowledgeDomainSVC knowledge.Knowledge // 知識庫領域服務PluginDomainSVC service.PluginService // 插件領域服務WorkflowDomainSVC workflow.Service // 工作流領域服務UserDomainSVC user.User // 用戶領域服務ConnectorDomainSVC connector.Connector // 連接器領域服務PromptDomainSVC prompt.Prompt // 提示詞領域服務DatabaseDomainSVC database.Database // 數據庫領域服務
}
服務初始化實現
文件位置:backend/application/search/init.go
// InitService 初始化搜索應用服務,注入所有依賴并設置消息隊列消費者
func InitService(ctx context.Context, s *ServiceComponents) (*SearchApplicationService, error) {// 創建搜索領域服務searchDomainSVC := search.NewDomainService(ctx, s.ESClient)// 注入依賴到全局服務實例SearchSVC.DomainSVC = searchDomainSVCSearchSVC.ServiceComponents = s// 設置項目搜索消費者searchConsumer := search.NewProjectHandler(ctx, s.ESClient)logs.Infof("start search domain consumer...")nameServer := os.Getenv(consts.MQServer)// 注冊項目事件消費者err := eventbus.DefaultSVC().RegisterConsumer(nameServer, consts.RMQTopicApp, consts.RMQConsumeGroupApp, searchConsumer)if err != nil {return nil, fmt.Errorf("register search consumer failed, err=%w", err)}// 設置資源搜索消費者searchResourceConsumer := search.NewResourceHandler(ctx, s.ESClient)// 注冊資源事件消費者err = eventbus.DefaultSVC().RegisterConsumer(nameServer, consts.RMQTopicResource, consts.RMQConsumeGroupResource, searchResourceConsumer)if err != nil {return nil, fmt.Errorf("register search consumer failed, err=%w", err)}return SearchSVC, nil
}// 事件總線類型別名
type (ResourceEventBus = search.ResourceEventBusProjectEventBus = search.ProjectEventBus
)// NewResourceEventBus 創建資源事件總線
func NewResourceEventBus(p eventbus.Producer) search.ResourceEventBus {return search.NewResourceEventBus(p)
}// NewProjectEventBus 創建項目事件總線
func NewProjectEventBus(p eventbus.Producer) search.ProjectEventBus {return search.NewProjectEventBus(p)
}
服務初始化特點:
- 依賴注入:通過ServiceComponents結構體注入15個不同的領域服務,實現完整的業務功能支持
- Elasticsearch集成:使用ES客戶端提供強大的全文搜索和索引功能
- 事件驅動架構:集成項目和資源事件總線,支持異步事件處理和數據同步
- 消息隊列消費者:自動注冊項目和資源的MQ消費者,實現實時數據更新
- 多領域服務協調:整合智能體、APP、知識庫、插件、工作流等多個領域服務
- 存儲服務集成:支持數據庫持久化、緩存加速和對象存儲
應用搜索服務核心實現
SearchApplicationService實現了搜索相關的核心業務邏輯,主要包括項目搜索、資源搜索、收藏管理等功能。
項目列表獲取功能
文件位置:backend/application/search/project_search.go
// GetDraftIntelligenceList 獲取草稿智能體項目列表
func (s *SearchApplicationService) GetDraftIntelligenceList(ctx context.Context, req *intelligence.GetDraftIntelligenceListRequest) (resp *intelligence.GetDraftIntelligenceListResponse, err error,
) {// 權限驗證:檢查用戶登錄狀態userID := ctxutil.GetUIDFromCtx(ctx)if userID == nil {return nil, errorx.New(errno.ErrSearchPermissionCode, errorx.KV("msg", "session is required"))}// 構建搜索請求do := searchRequestTo2Do(*userID, req)// 調用領域服務進行項目搜索searchResp, err := s.DomainSVC.SearchProjects(ctx, do)if err != nil {return nil, err}// 處理空結果if len(searchResp.Data) == 0 {return &intelligence.GetDraftIntelligenceListResponse{Data: &intelligence.DraftIntelligenceListData{Intelligences: make([]*intelligence.IntelligenceData, 0),Total: 0,HasMore: false,NextCursorID: "",},}, nil}// 并發處理項目數據封裝tasks := taskgroup.NewUninterruptibleTaskGroup(ctx, len(searchResp.Data))lock := sync.Mutex{}intelligenceDataList := make([]*intelligence.IntelligenceData, len(searchResp.Data))// 并發處理除第一個項目外的所有項目if len(searchResp.Data) > 1 {for idx := range searchResp.Data[1:] {index := idx + 1data := searchResp.Data[index]tasks.Go(func() error {info, err := s.packIntelligenceData(ctx, data)if err != nil {logs.CtxErrorf(ctx, "[packIntelligenceData] failed id %v, type %d , name %s, err: %v", data.ID, data.Type, data.GetName(), err)return err}lock.Lock()defer lock.Unlock()intelligenceDataList[index] = inforeturn nil})}}// 同步處理第一個項目(優先顯示)if len(searchResp.Data) != 0 {info, err := s.packIntelligenceData(ctx, searchResp.Data[0])if err != nil {logs.CtxErrorf(ctx, "[packIntelligenceData] failed id %v, type %d , name %s, err: %v", searchResp.Data[0].ID, searchResp.Data[0].Type, searchResp.Data[0].GetName(), err)return nil, err}lock.Lock()intelligenceDataList[0] = infolock.Unlock()}// 等待所有并發任務完成err = tasks.Wait()if err != nil {return nil, err}// 過濾空數據filterDataList := make([]*intelligence.IntelligenceData, 0)for _, data := range intelligenceDataList {if data != nil {filterDataList = append(filterDataList, data)}}return &intelligence.GetDraftIntelligenceListResponse{Code: 0,Data: &intelligence.DraftIntelligenceListData{Intelligences: filterDataList,Total: int32(len(filterDataList)),HasMore: searchResp.HasMore,NextCursorID: searchResp.NextCursor,},}, nil
}
代碼功能:
在用戶登錄后獲取項目列表的場景中(如 GetDraftIntelligenceList 方法),系統會:
- 首先通過搜索服務獲取項目列表: searchResp, err := s.DomainSVC.SearchProjects(ctx, do)
- 然后 循環遍歷 每個項目,為每個項目調用 packIntelligenceData
- 在 packIntelligenceData 中,為每個項目創建對應的 packer 并調用 GetProjectInfo獲取具體Project的信息。
func (s *SearchApplicationService) packIntelligenceData(ctx context.Context, doc *searchEntity.ProjectDocument) (*intelligence.IntelligenceData, error) {intelligenceData := &intelligence.IntelligenceData{Type: doc.Type,BasicInfo: &common.IntelligenceBasicInfo{ID: doc.ID,Name: doc.GetName(),SpaceID: doc.GetSpaceID(),OwnerID: doc.GetOwnerID(),Status: doc.Status,CreateTime: doc.GetCreateTime() / 1000,UpdateTime: doc.GetUpdateTime() / 1000,PublishTime: doc.GetPublishTime() / 1000,},}uid := ctxutil.MustGetUIDFromCtx(ctx)packer, err := NewPackProject(uid, doc.ID, doc.Type, s)if err != nil {return nil, err}projInfo, err := packer.GetProjectInfo(ctx)if err != nil {return nil, errorx.Wrapf(err, "GetProjectInfo failed, id: %v, type: %v", doc.ID, doc.Type)}intelligenceData.BasicInfo.Description = projInfo.descintelligenceData.BasicInfo.IconURI = projInfo.iconURIintelligenceData.BasicInfo.IconURL = s.getProjectIconURL(ctx, projInfo.iconURI, doc.Type)intelligenceData.PermissionInfo = packer.GetPermissionInfo()publishedInf := packer.GetPublishedInfo(ctx)if publishedInf != nil {intelligenceData.PublishInfo = packer.GetPublishedInfo(ctx)} else {intelligenceData.PublishInfo = &intelligence.IntelligencePublishInfo{HasPublished: false,}}intelligenceData.OwnerInfo = packer.GetUserInfo(ctx, doc.GetOwnerID())intelligenceData.LatestAuditInfo = &common.AuditInfo{}intelligenceData.FavoriteInfo = s.buildProjectFavoriteInfo(doc)intelligenceData.OtherInfo = s.buildProjectOtherInfo(doc)return intelligenceData, nil
}
代碼功能:
這段代碼是 SearchApplicationService 中的 packIntelligenceData 方法,用于將搜索文檔數據轉換為智能體數據結構。主要功能包括:
核心功能
數據轉換與封裝 :將 searchEntity.ProjectDocument 轉換為 intelligence.IntelligenceData 結構
主要處理步驟
- 基礎信息構建
- 創建 IntelligenceData 結構體
- 填充基礎信息:ID、名稱、空間ID、所有者ID、狀態
- 時間戳轉換:將毫秒時間戳轉換為秒(除以1000)
項目信息獲取 - 從上下文獲取用戶ID ( ctxutil.MustGetUIDFromCtx )
- 創建項目打包器 ( NewPackProject )
- 獲取項目詳細信息 ( GetProjectInfo )
詳細信息填充 - 描述和圖標 :從項目信息中獲取描述、圖標URI和圖標URL
- 權限信息 :通過打包器獲取權限信息
- 發布信息 :檢查是否有發布信息,沒有則設置為未發布狀態
- 用戶信息 :獲取所有者的用戶信息
- 其他信息 :構建審計信息、收藏信息和其他擴展信息
設計模式
使用了 Builder 模式 和 Adapter 模式 ,通過 packer 對象統一處理不同類型項目的信息獲取和轉換,實現了數據結構的標準化封裝。
這是典型的 數據傳輸對象(DTO)轉換 方法,用于搜索服務中將內部數據結構轉換為前端展示所需的格式
GetProjectInfo詳解
文件位置:backend\application\search\project_pack.go
接口定義核心代碼:
type ProjectPacker interface {GetProjectInfo(ctx context.Context) (*projectInfo, error)// 其他方法...
}
agentPacker實現核心代碼:
func (a *agentPacker) GetProjectInfo(ctx context.Context) (*projectInfo, error) {agent, err := a.SVC.SingleAgentDomainSVC.GetSingleAgentDraft(ctx, a.projectID)if err != nil {return nil, err}if agent == nil {return nil, fmt.Errorf("agent info is nil")}return &projectInfo{iconURI: agent.IconURI,desc: agent.Desc,}, nil
}
appPacker實現核心代碼:
func (a *appPacker) GetProjectInfo(ctx context.Context) (*projectInfo, error) {app, err := a.SVC.APPDomainSVC.GetDraftAPP(ctx, a.projectID)if err != nil {return nil, err}return &projectInfo{iconURI: app.GetIconURI(),desc: app.GetDesc(),}, nil
}
說明:
- GetProjectInfo 方法有兩個不同的實現,分別用于處理不同類型的項目
- agentPacker 用于處理 Bot 類型的項目( IntelligenceType_Bot )
- appPacker 用于處理 Project 類型的項目( IntelligenceType_Project )
- 兩個實現都返回包含 iconURI 和 desc 字段的 projectInfo 結構體
- 具體使用哪個實現取決于項目類型,通過 NewPackProject 工廠函數來創建相應的實現
4. 領域服務層
搜索領域服務接口
文件位置:backend\domain\search\service\service.go
核心代碼:
package serviceimport ("context""github.com/coze-dev/coze-studio/backend/domain/search/entity"
)type ProjectEventBus interface {PublishProject(ctx context.Context, event *entity.ProjectDomainEvent) error
}type ResourceEventBus interface {PublishResources(ctx context.Context, event *entity.ResourceDomainEvent) error
}type Search interface {SearchProjects(ctx context.Context, req *entity.SearchProjectsRequest) (resp *entity.SearchProjectsResponse, err error)SearchResources(ctx context.Context, req *entity.SearchResourcesRequest) (resp *entity.SearchResourcesResponse, err error)
}
搜索領域服務實現-業務接口
文件位置:backend\domain\search\service\search.go
核心代碼:
package serviceimport ("context""strconv""github.com/bytedance/sonic"model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity""github.com/coze-dev/coze-studio/backend/infra/contract/es""github.com/coze-dev/coze-studio/backend/pkg/lang/conv""github.com/coze-dev/coze-studio/backend/pkg/lang/ptr""github.com/coze-dev/coze-studio/backend/pkg/logs"
)var searchInstance *searchImplfunc NewDomainService(ctx context.Context, e es.Client) Search {return &searchImpl{esClient: e,}
}type searchImpl struct {esClient es.Client
}func (s *searchImpl) SearchProjects(ctx context.Context, req *searchEntity.SearchProjectsRequest) (resp *searchEntity.SearchProjectsResponse, err error) {logs.CtxDebugf(ctx, "[SearchProjects] search : %s", conv.DebugJsonToStr(req))searchReq := &es.Request{Query: &es.Query{Bool: &es.BoolQuery{},},}result, err := s.esClient.Search(ctx, projectIndexName, searchReq)if err != nil {logs.CtxDebugf(ctx, "[Serarch.DO] err : %v", err)return nil, err}hits := result.Hits.HitshasMore := func() bool {if len(hits) > reqLimit {return true}return false}()if hasMore {hits = hits[:reqLimit]}docs := make([]*searchEntity.ProjectDocument, 0, len(hits))for _, hit := range hits {doc, err := hit2AppDocument(hit)if err != nil {return nil, err}docs = append(docs, doc)}nextCursor := ""if len(docs) > 0 {nextCursor = formatProjectNextCursor(req.OrderFiledName, docs[len(docs)-1])}if nextCursor == "" {hasMore = false}resp = &searchEntity.SearchProjectsResponse{Data: docs,HasMore: hasMore,NextCursor: nextCursor,}return resp, nil
}
APP領域服務接口
文件位置:backend/domain/app/service/service.go
領域服務層定義了項目開發的核心業務接口,封裝了復雜的業務邏輯和數據操作。
核心代碼:
type AppService interface {GetDraftAPP(ctx context.Context, req *GetDraftAPPRequest) (*entity.APP, error)}
領域服務特點:
- 業務抽象:定義了智能體應用開發的核心業務操作,包括CRUD和發布管理
- 狀態管理:管理應用從草稿到發布的狀態轉換和生命周期
- 版本控制:支持應用的版本管理和發布歷史記錄
- 權限控制:通過OwnerID確保用戶只能操作自己的應用
- 資源管理:管理應用相關的資源,如圖標、配置等
- 連接器集成:支持應用發布時的連接器配置和管理
APP領域服務實現-業務接口
文件位置:backend/domain/app/service/service_impl.go
核心代碼:
func (a *appServiceImpl) GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, err error) {app, exist, err := a.APPRepo.GetDraftAPP(ctx, appID)if err != nil {return nil, err}if !exist {return nil, errorx.New(errno.ErrAppRecordNotFound)}return app, nil
}
APP領域服務層實現-業務實體
文件位置:backend/domain/app/entity/app.go
實體模型定義了智能體應用的核心數據結構和業務方法。
核心代碼:
package entity// APP 智能體應用實體
type APP struct {ID int64 `gorm:"column:id;primaryKey" json:"id"`SpaceID int64 `gorm:"column:space_id" json:"space_id"` // 工作空間IDIconURI string `gorm:"column:icon_uri" json:"icon_uri"` // 應用圖標URIName string `gorm:"column:name" json:"name"` // 應用名稱Desc string `gorm:"column:desc" json:"desc"` // 應用描述OwnerID int64 `gorm:"column:owner_id" json:"owner_id"` // 應用所有者IDConnectorIDs string `gorm:"column:connector_ids" json:"connector_ids"` // 連接器ID列表(JSON)Version string `gorm:"column:version" json:"version"` // 當前版本PublishRecordID int64 `gorm:"column:publish_record_id" json:"publish_record_id"` // 發布記錄IDPublishStatus int32 `gorm:"column:publish_status" json:"publish_status"` // 發布狀態PublishExtraInfo string `gorm:"column:publish_extra_info" json:"publish_extra_info"` // 發布額外信息CreatedAtMS int64 `gorm:"column:created_at_ms" json:"created_at_ms"` // 創建時間(毫秒)UpdatedAtMS int64 `gorm:"column:updated_at_ms" json:"updated_at_ms"` // 更新時間(毫秒)PublishedAtMS int64 `gorm:"column:published_at_ms" json:"published_at_ms"` // 發布時間(毫秒)
}
實體設計特點:
- 狀態管理:通過PublishStatus字段管理草稿、發布成功、發布失敗等狀態
- 工作空間隔離:通過SpaceID實現多工作空間的數據隔離
- 權限控制:通過OwnerID確保應用的所有權管理
- 版本管理:支持應用的版本號和發布記錄管理
- 連接器集成:通過ConnectorIDs字段管理應用的連接器配置
- 時間追蹤:精確到毫秒的創建、更新、發布時間記錄
- 擴展信息:通過PublishExtraInfo字段存儲發布相關的額外信息
單Agent領域服務接口
文件位置:backend\domain\agent\singleagent\service\single_agent.go
核心代碼:
package singleagentimport ("context""github.com/cloudwego/eino/schema""github.com/coze-dev/coze-studio/backend/api/model/playground""github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
)type SingleAgent interface {GetSingleAgentDraft(ctx context.Context, agentID int64) (agentInfo *entity.SingleAgent, err error)}
單Agent領域服務實現-業務接口
文件位置:domain\agent\singleagent\service\single_agent_impl.go
核心代碼:
func (s *singleAgentImpl) GetSingleAgentDraft(ctx context.Context, agentID int64) (*entity.SingleAgent, error) {return s.AgentDraftRepo.Get(ctx, agentID)
}
單Agent領域服務層實現-業務實體
文件位置:backend/domain/agent/singleagent/entity/single_agent.go
核心代碼:
package entity
import ("github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent"
)// Use composition instead of aliasing for domain entities to enhance extensibility
type SingleAgent struct {*singleagent.SingleAgent
}type AgentIdentity = singleagent.AgentIdentity
文件位置:backend/api/model/crossdomain/singleagent/single_agent.go
核心代碼:
package singleagenttype SingleAgent struct {AgentID int64CreatorID int64SpaceID int64Name stringDesc stringIconURI stringCreatedAt int64UpdatedAt int64Version stringDeletedAt gorm.DeletedAtVariablesMetaID *int64OnboardingInfo *bot_common.OnboardingInfoModelInfo *bot_common.ModelInfoPrompt *bot_common.PromptInfoPlugin []*bot_common.PluginInfoKnowledge *bot_common.KnowledgeWorkflow []*bot_common.WorkflowInfoSuggestReply *bot_common.SuggestReplyInfoJumpConfig *bot_common.JumpConfigBackgroundImageInfoList []*bot_common.BackgroundImageInfoDatabase []*bot_common.DatabaseBotMode bot_common.BotModeLayoutInfo *bot_common.LayoutInfoShortcutCommand []string
}type AgentIdentity struct {AgentID int64// State AgentStateVersion stringIsDraft boolConnectorID int64
}
5. 數據訪問層
AppRepo倉儲接口定義
文件位置:backend/domain/app/repository/app.go
type AppRepository interface {GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, exist bool, err error)
}
AppRepo倉儲實現
文件位置:backend/domain/app/repository/app_impl.go
func (a *appRepoImpl) GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, exist bool, err error) {return a.appDraftDAO.Get(ctx, appID)
}
GetDraftAPP接口功能分析:
- 接口職責:根據應用ID獲取草稿狀態的智能體應用信息
- 參數驗證:接收上下文和應用ID作為參數
- 數據查詢:通過倉儲層調用DAO層查詢草稿應用數據
- 存在性檢查:驗證應用是否存在,不存在時返回特定錯誤
- 錯誤處理:統一的錯誤處理和包裝機制
- 返回結果:返回完整的APP實體對象
設計特點:
- 分層架構:嚴格遵循領域驅動設計,服務層調用倉儲層
- 錯誤處理:使用統一的錯誤包裝機制,提供清晰的錯誤信息
- 數據完整性:確保返回的應用數據完整且有效
- 接口簡潔:接口設計簡潔明了,職責單一
- 類型安全:使用強類型參數和返回值,確保類型安全
數據訪問(app_draft.go)
文件位置:backend\domain\app\internal\dal\app_draft.go
func (a *APPDraftDAO) Get(ctx context.Context, appID int64) (app *entity.APP, exist bool, err error) {table := a.query.AppDraftres, err := table.WithContext(ctx).Where(table.ID.Eq(appID)).First()if err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {return nil, false, nil}return nil, false, err}app = appDraftPO(*res).ToDO()return app, true, nil
}
代碼功能:
- 數據庫查詢 - 使用GORM查詢框架,從 AppDraft 表中根據ID查找記錄
- 錯誤處理 - 區分處理兩種情況:
- 記錄不存在:返回 (nil, false, nil)
- 其他數據庫錯誤:返回 (nil, false, err)
- 數據轉換 - 將數據庫持久化對象(PO)轉換為領域實體(DO)
- appDraftPO(*res).ToDO() 執行PO到DO的轉換
- 成功返回 - 找到記錄時返回 (app, true, nil)
SingleAgentRepo倉儲接口定義
文件位置:backend\domain\agent\singleagent\repository\repository.go
package repositoryimport ("context""gorm.io/gorm""github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity""github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal")func NewSingleAgentRepo(db *gorm.DB, idGen idgen.IDGenerator, cli cache.Cmdable) SingleAgentDraftRepo {return dal.NewSingleAgentDraftDAO(db, idGen, cli)
}type SingleAgentDraftRepo interface {Get(ctx context.Context, agentID int64) (*entity.SingleAgent, error)
}
SingleAgentRepo倉儲實現
文件位置:backend\domain\agent\singleagent\internal\dal\single_agent_draft.go
type SingleAgentDraftDAO struct {idGen idgen.IDGeneratordbQuery *query.QuerycacheClient cache.Cmdable
}func NewSingleAgentDraftDAO(db *gorm.DB, idGen idgen.IDGenerator, cli cache.Cmdable) *SingleAgentDraftDAO {query.SetDefault(db)return &SingleAgentDraftDAO{idGen: idGen,dbQuery: query.Use(db),cacheClient: cli,}
}func (sa *SingleAgentDraftDAO) Get(ctx context.Context, agentID int64) (*entity.SingleAgent, error) {singleAgentDAOModel := sa.dbQuery.SingleAgentDraftsingleAgent, err := sa.dbQuery.SingleAgentDraft.Where(singleAgentDAOModel.AgentID.Eq(agentID)).First()if errors.Is(err, gorm.ErrRecordNotFound) {return nil, nil}if err != nil {return nil, errorx.WrapByCode(err, errno.ErrAgentGetCode)}do := sa.singleAgentDraftPo2Do(singleAgent)return do, nil
}
數據模型層
AppRepo-統一數據查詢(query\gen.go)
文件位置:backend\domain\app\internal\dal\query\gen.go
package queryimport ("context""database/sql""gorm.io/gorm""gorm.io/gen""gorm.io/plugin/dbresolver"
)var (Q = new(Query)AppConnectorReleaseRef *appConnectorReleaseRefAppDraft *appDraftAppReleaseRecord *appReleaseRecord
)func SetDefault(db *gorm.DB, opts ...gen.DOOption) {*Q = *Use(db, opts...)AppConnectorReleaseRef = &Q.AppConnectorReleaseRefAppDraft = &Q.AppDraftAppReleaseRecord = &Q.AppReleaseRecord
}func Use(db *gorm.DB, opts ...gen.DOOption) *Query {return &Query{db: db,AppConnectorReleaseRef: newAppConnectorReleaseRef(db, opts...),AppDraft: newAppDraft(db, opts...),AppReleaseRecord: newAppReleaseRecord(db, opts...),}
}type Query struct {db *gorm.DBAppConnectorReleaseRef appConnectorReleaseRefAppDraft appDraftAppReleaseRecord appReleaseRecord
}
代碼作用:
- 初始化管理
- SetDefault() : 設置全局默認查詢實例
- Use() : 創建新的查詢實例,初始化各表的查詢器
- 數據庫連接管理
- Available() : 檢查數據庫連接是否可用
- clone() : 克隆查詢實例
- ReadDB() / WriteDB() : 支持讀寫分離
- ReplaceDB() : 替換數據庫連接
- 上下文支持
- WithContext() : 為查詢操作添加上下文,返回帶接口的查詢上下文
- 事務管理
- Transaction() : 執行事務操作
- Begin() : 開始事務,返回 QueryTx
- QueryTx : 事務查詢器,支持 Commit、Rollback、SavePoint 等操作
這是典型的 Repository 模式 實現,為 app 領域的數據訪問提供了統一、類型安全的接口,支持事務、讀寫分離等高級數據庫功能。
AppRepo-app_draft數據查詢
文件位置:backend\domain\app\internal\dal\query\app_draft.gen.go
package queryimport ("context""gorm.io/gorm""gorm.io/gorm/clause""gorm.io/gorm/schema""gorm.io/gen""gorm.io/gen/field""gorm.io/plugin/dbresolver""github.com/coze-dev/coze-studio/backend/domain/app/internal/dal/model"
)func newAppDraft(db *gorm.DB, opts ...gen.DOOption) appDraft {_appDraft := appDraft{}_appDraft.appDraftDo.UseDB(db, opts...)_appDraft.appDraftDo.UseModel(&model.AppDraft{})tableName := _appDraft.appDraftDo.TableName()_appDraft.ALL = field.NewAsterisk(tableName)_appDraft.ID = field.NewInt64(tableName, "id")_appDraft.SpaceID = field.NewInt64(tableName, "space_id")_appDraft.OwnerID = field.NewInt64(tableName, "owner_id")_appDraft.IconURI = field.NewString(tableName, "icon_uri")_appDraft.Name = field.NewString(tableName, "name")_appDraft.Description = field.NewString(tableName, "description")_appDraft.CreatedAt = field.NewInt64(tableName, "created_at")_appDraft.UpdatedAt = field.NewInt64(tableName, "updated_at")_appDraft.DeletedAt = field.NewField(tableName, "deleted_at")_appDraft.fillFieldMap()return _appDraft
}// appDraft Draft Application
type appDraft struct {appDraftDoALL field.AsteriskID field.Int64 // APP IDSpaceID field.Int64 // Space IDOwnerID field.Int64 // Owner IDIconURI field.String // Icon URIName field.String // Application NameDescription field.String // Application DescriptionCreatedAt field.Int64 // Create Time in MillisecondsUpdatedAt field.Int64 // Update Time in MillisecondsDeletedAt field.Field // Delete TimefieldMap map[string]field.Expr
}type IAppDraftDo interface {gen.SubQueryDebug() IAppDraftDoWithContext(ctx context.Context) IAppDraftDoWithResult(fc func(tx gen.Dao)) gen.ResultInfoReplaceDB(db *gorm.DB)ReadDB() IAppDraftDoWriteDB() IAppDraftDoAs(alias string) gen.DaoSession(config *gorm.Session) IAppDraftDoColumns(cols ...field.Expr) gen.ColumnsClauses(conds ...clause.Expression) IAppDraftDoNot(conds ...gen.Condition) IAppDraftDoOr(conds ...gen.Condition) IAppDraftDoSelect(conds ...field.Expr) IAppDraftDoWhere(conds ...gen.Condition) IAppDraftDoOrder(conds ...field.Expr) IAppDraftDoDistinct(cols ...field.Expr) IAppDraftDoOmit(cols ...field.Expr) IAppDraftDoJoin(table schema.Tabler, on ...field.Expr) IAppDraftDoLeftJoin(table schema.Tabler, on ...field.Expr) IAppDraftDoRightJoin(table schema.Tabler, on ...field.Expr) IAppDraftDoGroup(cols ...field.Expr) IAppDraftDoHaving(conds ...gen.Condition) IAppDraftDoLimit(limit int) IAppDraftDoOffset(offset int) IAppDraftDoCount() (count int64, err error)Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDraftDoUnscoped() IAppDraftDoCreate(values ...*model.AppDraft) errorCreateInBatches(values []*model.AppDraft, batchSize int) errorSave(values ...*model.AppDraft) errorFirst() (*model.AppDraft, error)Take() (*model.AppDraft, error)Last() (*model.AppDraft, error)Find() ([]*model.AppDraft, error)FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppDraft, err error)FindInBatches(result *[]*model.AppDraft, batchSize int, fc func(tx gen.Dao, batch int) error) errorPluck(column field.Expr, dest interface{}) errorDelete(...*model.AppDraft) (info gen.ResultInfo, err error)Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)Updates(value interface{}) (info gen.ResultInfo, err error)UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)UpdateColumns(value interface{}) (info gen.ResultInfo, err error)UpdateFrom(q gen.SubQuery) gen.DaoAttrs(attrs ...field.AssignExpr) IAppDraftDoAssign(attrs ...field.AssignExpr) IAppDraftDoJoins(fields ...field.RelationField) IAppDraftDoPreload(fields ...field.RelationField) IAppDraftDoFirstOrInit() (*model.AppDraft, error)FirstOrCreate() (*model.AppDraft, error)FindByPage(offset int, limit int) (result []*model.AppDraft, count int64, err error)ScanByPage(result interface{}, offset int, limit int) (count int64, err error)Scan(result interface{}) (err error)Returning(value interface{}, columns ...string) IAppDraftDoUnderlyingDB() *gorm.DBschema.Tabler
}func (a appDraftDo) Debug() IAppDraftDo {return a.withDO(a.DO.Debug())
}func (a appDraftDo) WithContext(ctx context.Context) IAppDraftDo {return a.withDO(a.DO.WithContext(ctx))
}
代碼作用:
這是典型的 ORM 查詢構建器模式 ,為 app_draft 表提供了類型安全、功能豐富的數據庫操作接口。
AppRepo-app_draft數據模型
文件位置:backend\domain\app\internal\dal\model\app_draft.gen.go
package modelimport ("gorm.io/gorm"
)const TableNameAppDraft = "app_draft"// AppDraft Draft Application
type AppDraft struct {ID int64 `gorm:"column:id;primaryKey;comment:APP ID" json:"id"` // APP IDSpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space IDOwnerID int64 `gorm:"column:owner_id;not null;comment:Owner ID" json:"owner_id"` // Owner IDIconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URIName string `gorm:"column:name;not null;comment:Application Name" json:"name"` // Application NameDescription string `gorm:"column:description;comment:Application Description" json:"description"` // Application DescriptionCreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in MillisecondsUpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in MillisecondsDeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time
}// TableName AppDraft's table name
func (*AppDraft) TableName() string {return TableNameAppDraft
}
代碼作用:
- 1.數據模型定義 - 定義了 AppDraft 結構體,對應數據庫中的 app_draft 表
- 2.字段映射 - 通過GORM標簽將Go結構體字段映射到數據庫表列
- 3.表名綁定 - 通過 TableName() 方法指定對應的數據庫表名
SingleAgentRepo-統一數據查詢(query\gen.go)
文件位置:backend\domain\agent\singleagent\internal\dal\query\gen.go
package queryimport ("context""database/sql""gorm.io/gorm""gorm.io/gen""gorm.io/plugin/dbresolver"
)var (Q = new(Query)SingleAgentDraft *singleAgentDraftSingleAgentPublish *singleAgentPublishSingleAgentVersion *singleAgentVersion
)func SetDefault(db *gorm.DB, opts ...gen.DOOption) {*Q = *Use(db, opts...)SingleAgentDraft = &Q.SingleAgentDraftSingleAgentPublish = &Q.SingleAgentPublishSingleAgentVersion = &Q.SingleAgentVersion
}func Use(db *gorm.DB, opts ...gen.DOOption) *Query {return &Query{db: db,SingleAgentDraft: newSingleAgentDraft(db, opts...),SingleAgentPublish: newSingleAgentPublish(db, opts...),SingleAgentVersion: newSingleAgentVersion(db, opts...),}
}type Query struct {db *gorm.DBSingleAgentDraft singleAgentDraftSingleAgentPublish singleAgentPublishSingleAgentVersion singleAgentVersion
}
SingleAgentRepo-single_agent_draft數據查詢
文件位置:backend\domain\agent\singleagent\internal\dal\query\single_agent_draft.gen.go
package queryimport ("context""gorm.io/gorm""gorm.io/gorm/clause""gorm.io/gorm/schema""gorm.io/gen""gorm.io/gen/field""gorm.io/plugin/dbresolver""github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal/model"
)func newSingleAgentDraft(db *gorm.DB, opts ...gen.DOOption) singleAgentDraft {_singleAgentDraft := singleAgentDraft{}_singleAgentDraft.singleAgentDraftDo.UseDB(db, opts...)_singleAgentDraft.singleAgentDraftDo.UseModel(&model.SingleAgentDraft{})tableName := _singleAgentDraft.singleAgentDraftDo.TableName()_singleAgentDraft.ALL = field.NewAsterisk(tableName)_singleAgentDraft.ID = field.NewInt64(tableName, "id")_singleAgentDraft.AgentID = field.NewInt64(tableName, "agent_id")_singleAgentDraft.CreatorID = field.NewInt64(tableName, "creator_id")_singleAgentDraft.SpaceID = field.NewInt64(tableName, "space_id")_singleAgentDraft.Name = field.NewString(tableName, "name")_singleAgentDraft.Description = field.NewString(tableName, "description")_singleAgentDraft.IconURI = field.NewString(tableName, "icon_uri")_singleAgentDraft.CreatedAt = field.NewInt64(tableName, "created_at")_singleAgentDraft.UpdatedAt = field.NewInt64(tableName, "updated_at")_singleAgentDraft.DeletedAt = field.NewField(tableName, "deleted_at")_singleAgentDraft.VariablesMetaID = field.NewInt64(tableName, "variables_meta_id")_singleAgentDraft.ModelInfo = field.NewField(tableName, "model_info")_singleAgentDraft.OnboardingInfo = field.NewField(tableName, "onboarding_info")_singleAgentDraft.Prompt = field.NewField(tableName, "prompt")_singleAgentDraft.Plugin = field.NewField(tableName, "plugin")_singleAgentDraft.Knowledge = field.NewField(tableName, "knowledge")_singleAgentDraft.Workflow = field.NewField(tableName, "workflow")_singleAgentDraft.SuggestReply = field.NewField(tableName, "suggest_reply")_singleAgentDraft.JumpConfig = field.NewField(tableName, "jump_config")_singleAgentDraft.BackgroundImageInfoList = field.NewField(tableName, "background_image_info_list")_singleAgentDraft.DatabaseConfig = field.NewField(tableName, "database_config")_singleAgentDraft.BotMode = field.NewInt32(tableName, "bot_mode")_singleAgentDraft.ShortcutCommand = field.NewField(tableName, "shortcut_command")_singleAgentDraft.LayoutInfo = field.NewField(tableName, "layout_info")_singleAgentDraft.fillFieldMap()return _singleAgentDraft
}// singleAgentDraft Single Agent Draft Copy Table
type singleAgentDraft struct {singleAgentDraftDoALL field.AsteriskID field.Int64 // Primary Key IDAgentID field.Int64 // Agent IDCreatorID field.Int64 // Creator IDSpaceID field.Int64 // Space IDName field.String // Agent NameDescription field.String // Agent DescriptionIconURI field.String // Icon URICreatedAt field.Int64 // Create Time in MillisecondsUpdatedAt field.Int64 // Update Time in MillisecondsDeletedAt field.Field // delete time in millisecondVariablesMetaID field.Int64 // variables meta table IDModelInfo field.Field // Model Configuration InformationOnboardingInfo field.Field // Onboarding InformationPrompt field.Field // Agent Prompt ConfigurationPlugin field.Field // Agent Plugin Base ConfigurationKnowledge field.Field // Agent Knowledge Base ConfigurationWorkflow field.Field // Agent Workflow ConfigurationSuggestReply field.Field // Suggested RepliesJumpConfig field.Field // Jump ConfigurationBackgroundImageInfoList field.Field // Background imageDatabaseConfig field.Field // Agent Database Base ConfigurationBotMode field.Int32 // mod,0:single mode 2:chatflow modeShortcutCommand field.Field // shortcut commandLayoutInfo field.Field // chatflow layout infofieldMap map[string]field.Expr
}func (s singleAgentDraft) Table(newTableName string) *singleAgentDraft {s.singleAgentDraftDo.UseTable(newTableName)return s.updateTableName(newTableName)
}func (s singleAgentDraft) As(alias string) *singleAgentDraft {s.singleAgentDraftDo.DO = *(s.singleAgentDraftDo.As(alias).(*gen.DO))return s.updateTableName(alias)
}func (s *singleAgentDraft) updateTableName(table string) *singleAgentDraft {s.ALL = field.NewAsterisk(table)s.ID = field.NewInt64(table, "id")s.AgentID = field.NewInt64(table, "agent_id")s.CreatorID = field.NewInt64(table, "creator_id")s.SpaceID = field.NewInt64(table, "space_id")s.Name = field.NewString(table, "name")s.Description = field.NewString(table, "description")s.IconURI = field.NewString(table, "icon_uri")s.CreatedAt = field.NewInt64(table, "created_at")s.UpdatedAt = field.NewInt64(table, "updated_at")s.DeletedAt = field.NewField(table, "deleted_at")s.VariablesMetaID = field.NewInt64(table, "variables_meta_id")s.ModelInfo = field.NewField(table, "model_info")s.OnboardingInfo = field.NewField(table, "onboarding_info")s.Prompt = field.NewField(table, "prompt")s.Plugin = field.NewField(table, "plugin")s.Knowledge = field.NewField(table, "knowledge")s.Workflow = field.NewField(table, "workflow")s.SuggestReply = field.NewField(table, "suggest_reply")s.JumpConfig = field.NewField(table, "jump_config")s.BackgroundImageInfoList = field.NewField(table, "background_image_info_list")s.DatabaseConfig = field.NewField(table, "database_config")s.BotMode = field.NewInt32(table, "bot_mode")s.ShortcutCommand = field.NewField(table, "shortcut_command")s.LayoutInfo = field.NewField(table, "layout_info")s.fillFieldMap()return s
}func (s *singleAgentDraft) GetFieldByName(fieldName string) (field.OrderExpr, bool) {_f, ok := s.fieldMap[fieldName]if !ok || _f == nil {return nil, false}_oe, ok := _f.(field.OrderExpr)return _oe, ok
}func (s *singleAgentDraft) fillFieldMap() {s.fieldMap = make(map[string]field.Expr, 24)s.fieldMap["id"] = s.IDs.fieldMap["agent_id"] = s.AgentIDs.fieldMap["creator_id"] = s.CreatorIDs.fieldMap["space_id"] = s.SpaceIDs.fieldMap["name"] = s.Names.fieldMap["description"] = s.Descriptions.fieldMap["icon_uri"] = s.IconURIs.fieldMap["created_at"] = s.CreatedAts.fieldMap["updated_at"] = s.UpdatedAts.fieldMap["deleted_at"] = s.DeletedAts.fieldMap["variables_meta_id"] = s.VariablesMetaIDs.fieldMap["model_info"] = s.ModelInfos.fieldMap["onboarding_info"] = s.OnboardingInfos.fieldMap["prompt"] = s.Prompts.fieldMap["plugin"] = s.Plugins.fieldMap["knowledge"] = s.Knowledges.fieldMap["workflow"] = s.Workflows.fieldMap["suggest_reply"] = s.SuggestReplys.fieldMap["jump_config"] = s.JumpConfigs.fieldMap["background_image_info_list"] = s.BackgroundImageInfoLists.fieldMap["database_config"] = s.DatabaseConfigs.fieldMap["bot_mode"] = s.BotModes.fieldMap["shortcut_command"] = s.ShortcutCommands.fieldMap["layout_info"] = s.LayoutInfo
}func (s singleAgentDraft) clone(db *gorm.DB) singleAgentDraft {s.singleAgentDraftDo.ReplaceConnPool(db.Statement.ConnPool)return s
}func (s singleAgentDraft) replaceDB(db *gorm.DB) singleAgentDraft {s.singleAgentDraftDo.ReplaceDB(db)return s
}type singleAgentDraftDo struct{ gen.DO }type ISingleAgentDraftDo interface {gen.SubQueryDebug() ISingleAgentDraftDoWithContext(ctx context.Context) ISingleAgentDraftDoWithResult(fc func(tx gen.Dao)) gen.ResultInfoReplaceDB(db *gorm.DB)ReadDB() ISingleAgentDraftDoWriteDB() ISingleAgentDraftDoAs(alias string) gen.DaoSession(config *gorm.Session) ISingleAgentDraftDoColumns(cols ...field.Expr) gen.ColumnsClauses(conds ...clause.Expression) ISingleAgentDraftDoNot(conds ...gen.Condition) ISingleAgentDraftDoOr(conds ...gen.Condition) ISingleAgentDraftDoSelect(conds ...field.Expr) ISingleAgentDraftDoWhere(conds ...gen.Condition) ISingleAgentDraftDoOrder(conds ...field.Expr) ISingleAgentDraftDoDistinct(cols ...field.Expr) ISingleAgentDraftDoOmit(cols ...field.Expr) ISingleAgentDraftDoJoin(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDoLeftJoin(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDoRightJoin(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDoGroup(cols ...field.Expr) ISingleAgentDraftDoHaving(conds ...gen.Condition) ISingleAgentDraftDoLimit(limit int) ISingleAgentDraftDoOffset(offset int) ISingleAgentDraftDoCount() (count int64, err error)Scopes(funcs ...func(gen.Dao) gen.Dao) ISingleAgentDraftDoUnscoped() ISingleAgentDraftDoCreate(values ...*model.SingleAgentDraft) errorCreateInBatches(values []*model.SingleAgentDraft, batchSize int) errorSave(values ...*model.SingleAgentDraft) errorFirst() (*model.SingleAgentDraft, error)Take() (*model.SingleAgentDraft, error)Last() (*model.SingleAgentDraft, error)Find() ([]*model.SingleAgentDraft, error)FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.SingleAgentDraft, err error)FindInBatches(result *[]*model.SingleAgentDraft, batchSize int, fc func(tx gen.Dao, batch int) error) errorPluck(column field.Expr, dest interface{}) errorDelete(...*model.SingleAgentDraft) (info gen.ResultInfo, err error)Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)Updates(value interface{}) (info gen.ResultInfo, err error)UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)UpdateColumns(value interface{}) (info gen.ResultInfo, err error)UpdateFrom(q gen.SubQuery) gen.DaoAttrs(attrs ...field.AssignExpr) ISingleAgentDraftDoAssign(attrs ...field.AssignExpr) ISingleAgentDraftDoJoins(fields ...field.RelationField) ISingleAgentDraftDoPreload(fields ...field.RelationField) ISingleAgentDraftDoFirstOrInit() (*model.SingleAgentDraft, error)FirstOrCreate() (*model.SingleAgentDraft, error)FindByPage(offset int, limit int) (result []*model.SingleAgentDraft, count int64, err error)ScanByPage(result interface{}, offset int, limit int) (count int64, err error)Scan(result interface{}) (err error)Returning(value interface{}, columns ...string) ISingleAgentDraftDoUnderlyingDB() *gorm.DBschema.Tabler
}
AppRepo-single_agent_draft數據模型
文件位置:backend\domain\agent\singleagent\internal\dal\model\single_agent_draft.gen.go
package modelimport ("github.com/coze-dev/coze-studio/backend/api/model/app/bot_common""gorm.io/gorm"
)const TableNameSingleAgentDraft = "single_agent_draft"// SingleAgentDraft Single Agent Draft Copy Table
type SingleAgentDraft struct {ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:Primary Key ID" json:"id"` // Primary Key IDAgentID int64 `gorm:"column:agent_id;not null;comment:Agent ID" json:"agent_id"` // Agent IDCreatorID int64 `gorm:"column:creator_id;not null;comment:Creator ID" json:"creator_id"` // Creator IDSpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space IDName string `gorm:"column:name;not null;comment:Agent Name" json:"name"` // Agent NameDescription string `gorm:"column:description;not null;comment:Agent Description" json:"description"` // Agent DescriptionIconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URICreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in MillisecondsUpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in MillisecondsDeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecondVariablesMetaID *int64 `gorm:"column:variables_meta_id;comment:variables meta table ID" json:"variables_meta_id"` // variables meta table IDModelInfo *bot_common.ModelInfo `gorm:"column:model_info;comment:Model Configuration Information;serializer:json" json:"model_info"` // Model Configuration InformationOnboardingInfo *bot_common.OnboardingInfo `gorm:"column:onboarding_info;comment:Onboarding Information;serializer:json" json:"onboarding_info"` // Onboarding InformationPrompt *bot_common.PromptInfo `gorm:"column:prompt;comment:Agent Prompt Configuration;serializer:json" json:"prompt"` // Agent Prompt ConfigurationPlugin []*bot_common.PluginInfo `gorm:"column:plugin;comment:Agent Plugin Base Configuration;serializer:json" json:"plugin"` // Agent Plugin Base ConfigurationKnowledge *bot_common.Knowledge `gorm:"column:knowledge;comment:Agent Knowledge Base Configuration;serializer:json" json:"knowledge"` // Agent Knowledge Base ConfigurationWorkflow []*bot_common.WorkflowInfo `gorm:"column:workflow;comment:Agent Workflow Configuration;serializer:json" json:"workflow"` // Agent Workflow ConfigurationSuggestReply *bot_common.SuggestReplyInfo `gorm:"column:suggest_reply;comment:Suggested Replies;serializer:json" json:"suggest_reply"` // Suggested RepliesJumpConfig *bot_common.JumpConfig `gorm:"column:jump_config;comment:Jump Configuration;serializer:json" json:"jump_config"` // Jump ConfigurationBackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background imageDatabaseConfig []*bot_common.Database `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"` // Agent Database Base ConfigurationBotMode int32 `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"` // mod,0:single mode 2:chatflow modeShortcutCommand []string `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"` // shortcut commandLayoutInfo *bot_common.LayoutInfo `gorm:"column:layout_info;comment:chatflow layout info;serializer:json" json:"layout_info"` // chatflow layout info
}// TableName SingleAgentDraft's table name
func (*SingleAgentDraft) TableName() string {return TableNameSingleAgentDraft
}
文件依賴關系
APP依賴層次:數據庫表結構 (schema.sql)↓ gen_orm_query.go
模型文件 (model/app_draft.gen.go) - 并行生成↓
查詢文件 (query/app_draft.gen.go) - 依賴對應模型↓
統一入口 (query/gen.go) - 依賴所有查詢文件
singleAgent依賴層次:數據庫表結構 (schema.sql)↓ gen_orm_query.go
模型文件 (model/single_agent_draft.gen.go) - 模型生成↓
查詢文件 (query/single_agent_draft.gen.go) - 依賴對應模型↓
統一入口 (query/gen.go) - 依賴所有查詢文件
6. 基礎設施層
database.go文件詳解
文件位置:backend/infra/contract/orm/database.go
核心代碼:
package ormimport ("gorm.io/gorm"
)type DB = gorm.DB
文件作用:
- 定義了 type DB = gorm.DB ,為 GORM 數據庫對象提供類型別名
- 作為契約層(Contract),為上層提供統一的數據庫接口抽象
- 便于后續可能的數據庫實現替換(如從 MySQL 切換到 PostgreSQL)
mysql.go文件詳解
文件位置:backend/infra/impl/mysql/mysql.go
核心代碼:
package mysqlimport ("fmt""os""gorm.io/driver/mysql""gorm.io/gorm"
)func New() (*gorm.DB, error) {dsn := os.Getenv("MYSQL_DSN")db, err := gorm.Open(mysql.Open(dsn))if err != nil {return nil, fmt.Errorf("mysql open, dsn: %s, err: %w", dsn, err)}return db, nil
}
文件作用:
- 定義了 New() 函數,負責建立 GORM MySQL 數據庫連接
- 使用環境變量 MYSQL_DSN 配置數據庫連接字符串
- 返回 *gorm.DB 實例,作為整個應用的數據庫連接對象
- 后端服務啟動時,調用 mysql.New() 初始化數據庫連接
main.go → application.Init() → appinfra.Init() → mysql.New()
ElasticSearch架構設計
Contract 層(接口定義)
backend/infra/contract/es/
目錄定義了 ElasticSearch 的抽象接口:
-
es.go
: 定義了核心接口Client
接口:包含Search
、Create
、Update
、Delete
、CreateIndex
等方法Types
接口:定義屬性類型創建方法BulkIndexer
接口:批量操作接口
-
model.go
: 定義數據模型Request
:搜索請求結構體,包含查詢條件、分頁、排序等Response
:搜索響應結構體,包含命中結果和元數據Hit
:單個搜索結果BulkIndexerItem
:批量操作項
-
query.go
: 定義查詢相關結構Query
:查詢結構體,支持多種查詢類型QueryType
常量:equal
、match
、multi_match
、not_exists
、contains
、in
BoolQuery
:布爾查詢,支持must
、should
、filter
、must_not
- 各種查詢構造函數:
NewEqualQuery
、NewMatchQuery
等
Implementation 層(具體實現)
backend/infra/impl/es/
目錄提供了具體實現:
-
es_impl.go
: 工廠方法New()
函數根據環境變量ES_VERSION
選擇 ES7 或 ES8 實現- 類型別名導出,統一接口
-
es7.go
: ElasticSearch 7.x 實現es7Client
結構體實現Client
接口- 使用
github.com/elastic/go-elasticsearch/v7
官方客戶端 Search
方法將抽象查詢轉換為 ES7 格式的 JSON 查詢query2ESQuery
方法處理查詢類型轉換
-
es8.go
: ElasticSearch 8.x 實現es8Client
結構體實現Client
接口- 使用
github.com/elastic/go-elasticsearch/v8
官方客戶端 - 使用類型化 API,更加類型安全
Search
方法使用 ES8 的 typed API
查詢執行流程
- 業務層調用:
backend/domain/search/service/search.go
中的SearchProjects
方法 - 構建查詢:創建
es.Request
對象,設置查詢條件、排序、分頁等 - 執行查詢:調用
s.esClient.Search(ctx, projectIndexName, searchReq)
- 版本適配:根據
ES_VERSION
環境變量,自動選擇 ES7 或 ES8 實現 - 查詢轉換:
- ES7:將抽象查詢轉換為 JSON 格式
- ES8:將抽象查詢轉換為類型化結構體
- 結果處理:將 ES 響應轉換為統一的
Response
結構體
索引使用:
- 項目索引:
projectIndexName = "project_draft"
存儲項目草稿信息 - 資源索引:
resourceIndexName = "coze_resource"
存儲各類資源信息
設計優勢
- 版本兼容:同時支持 ES7 和 ES8,通過環境變量切換
- 接口統一:業務代碼無需關心具體 ES 版本
- 類型安全:ES8 使用類型化 API,減少運行時錯誤
- 查詢抽象:提供統一的查詢構建方式,支持復雜的布爾查詢
- 易于擴展:新增查詢類型只需在 contract 層定義,impl 層實現
這種設計模式體現了依賴倒置原則,業務層依賴抽象接口而非具體實現,使得系統更加靈活和可維護。
7. 數據存儲層
數據庫表結構
文件位置:docker/volumes/mysql/schema.sql
-- 應用草稿表
CREATE TABLE IF NOT EXISTS `app_draft` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`space_id` bigint(20) NOT NULL COMMENT '工作空間ID',`owner_id` bigint(20) NOT NULL COMMENT '應用所有者ID',`icon_uri` varchar(255) NOT NULL COMMENT '應用圖標URI',`name` varchar(255) NOT NULL COMMENT '應用名稱',`description` text COMMENT '應用描述',`created_at` bigint(20) NOT NULL COMMENT '創建時間(毫秒級)',`updated_at` bigint(20) NOT NULL COMMENT '更新時間(毫秒級)',`deleted_at` bigint(20) DEFAULT NULL COMMENT '刪除時間(毫秒級)',PRIMARY KEY (`id`),KEY `idx_space_id` (`space_id`),KEY `idx_owner_id` (`owner_id`),KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='應用草稿表';-- 單智能體草稿表
CREATE TABLE IF NOT EXISTS `single_agent_draft` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID',`agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID',`creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID',`space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID',`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Name',`description` text NULL COMMENT 'Agent Description',`icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI',`created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds',`updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds',`deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond',`variables_meta_id` bigint NULL COMMENT 'variables meta table ID',`model_info` json NULL COMMENT 'Model Configuration Information',`onboarding_info` json NULL COMMENT 'Onboarding Information',`prompt` json NULL COMMENT 'Agent Prompt Configuration',`plugin` json NULL COMMENT 'Agent Plugin Base Configuration',`knowledge` json NULL COMMENT 'Agent Knowledge Base Configuration',`workflow` json NULL COMMENT 'Agent Workflow Configuration',`suggest_reply` json NULL COMMENT 'Suggested Replies',`jump_config` json NULL COMMENT 'Jump Configuration',`background_image_info_list` json NULL COMMENT 'Background image',`database_config` json NULL COMMENT 'Agent Database Base Configuration',`bot_mode` tinyint NOT NULL DEFAULT 0 COMMENT 'bot mode,0:single mode 2:chatflow mode',`layout_info` text NULL COMMENT 'chatflow layout info',`shortcut_command` json NULL COMMENT 'shortcut command',PRIMARY KEY (`id`),INDEX `idx_creator_id` (`creator_id`),UNIQUE INDEX `uniq_agent_id` (`agent_id`)
) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Single Agent Draft Copy Table';
project_draft ElasticSearch 索引結構
{"mappings": {"properties": {"id": {"type": "long","description": "項目唯一標識符"},"type": {"type": "integer","description": "智能體類型,枚舉值:1=Bot, 2=Project"},"status": {"type": "integer","description": "項目狀態,枚舉值:1=Using(使用中), 2=Deleted(已刪除), 3=Banned(已禁用), 4=MoveFailed(遷移失敗), 5=Copying(復制中), 6=CopyFailed(復制失敗)"},"name": {"type": "text","fields": {"raw": {"type": "keyword"}},"description": "項目名稱"},"space_id": {"type": "long","description": "工作空間ID"},"owner_id": {"type": "long","description": "項目所有者ID"},"has_published": {"type": "integer","description": "是否已發布,0=未發布, 1=已發布"},"create_time": {"type": "long","description": "創建時間(毫秒時間戳)"},"update_time": {"type": "long","description": "更新時間(毫秒時間戳)"},"publish_time": {"type": "long","description": "發布時間(毫秒時間戳)"},"recently_open_time": {"type": "long","description": "最近打開時間(毫秒時間戳)"},"fav_time": {"type": "long","description": "收藏時間(毫秒時間戳)"},"is_fav": {"type": "integer","description": "是否收藏,0=未收藏, 1=已收藏"},"is_recently_open": {"type": "integer","description": "是否最近打開,0=否, 1=是"}}}
}
字段說明:
該索引主要用于存儲項目草稿的元數據信息,支持以下功能:
- 基礎信息:項目ID、名稱、類型、狀態
- 權限管理:工作空間ID、所有者ID
- 時間追蹤:創建時間、更新時間、發布時間、最近打開時間、收藏時間
- 狀態標記:發布狀態、收藏狀態、最近打開狀態
- 搜索支持:項目名稱支持全文搜索和精確匹配
該索引與 MySQL 中的 app_draft
表類似,但專門用于 ElasticSearch 的高效搜索和查詢功能,支持復雜的搜索條件、排序和分頁操作。
8. 安全和權限驗證機制
用戶身份驗證流程
在項目開發功能中,系統需要驗證用戶身份以確保數據安全。整個身份驗證流程如下:
- 會話驗證:通過
ctxutil.GetUIDFromCtx(ctx)
從請求上下文中提取用戶ID - 工作空間隔離:確保用戶只能訪問所屬工作空間的應用
- 所有者權限驗證:驗證用戶對特定應用的所有權和操作權限
- 資源權限控制:驗證用戶對應用關聯資源的訪問權限
權限驗證實現
文件位置:backend/application/app/app.go
核心代碼:
// validateAPPPermission 驗證用戶對應用的操作權限
func (s *APPApplicationService) validateAPPPermission(ctx context.Context, appID int64, userID int64) error {// 獲取應用信息app, err := s.appDomainSVC.GetDraftAPP(ctx, &service.GetDraftAPPRequest{APPID: appID,OwnerID: userID,})if err != nil {return err}// 驗證應用所有權if app.OwnerID != userID {return errors.New("permission denied: user does not own this app")}// 驗證工作空間權限if !s.validateSpacePermission(ctx, userID, app.SpaceID) {return errors.New("permission denied: space access denied")}return nil
}// validateSpacePermission 驗證工作空間權限
func (s *APPApplicationService) validateSpacePermission(ctx context.Context, userID, spaceID int64) bool {// 檢查用戶是否有訪問該工作空間的權限return s.userSVC.HasSpaceAccess(ctx, userID, spaceID)
}func (s *APPApplicationService) DraftProjectUpdate(ctx context.Context, req *intelligence.DraftProjectUpdateRequest) (*intelligence.DraftProjectUpdateResponse, error) {resp := &intelligence.DraftProjectUpdateResponse{}userID := ctxutil.GetUIDFromCtx(ctx)// 驗證操作權限err := s.validateAPPPermission(ctx, req.ProjectID, userID)if err != nil {logs.CtxErrorf(ctx, "validateAPPPermission failed, err=%v", err)return resp, err}// 執行更新操作app, err := s.appDomainSVC.UpdateDraftAPP(ctx, &service.UpdateDraftAPPRequest{APPID: req.ProjectID,OwnerID: userID,Name: req.Name,Desc: req.Description,IconURI: req.IconURI,})if err != nil {logs.CtxErrorf(ctx, "appDomainSVC.UpdateDraftAPP failed, err=%v", err)return resp, err}resp.Project = s.convertToIntelligenceInfo(app)return resp, nil
}
安全機制特點:
- 身份驗證:每個請求都需要驗證用戶身份
- 權限隔離:用戶只能操作自己的項目
- 操作審計:記錄所有項目操作的日志
- 數據驗證:對輸入參數進行嚴格驗證
9. 錯誤處理和日志記錄
錯誤處理機制
在項目開發功能中,系統采用了分層錯誤處理機制:
- 參數驗證錯誤:通過
invalidParamRequestResponse
處理參數綁定和驗證錯誤,返回400狀態碼 - 權限驗證錯誤:專門處理權限驗證失敗的錯誤,返回403狀態碼
- 資源不存在錯誤:處理項目不存在等資源錯誤,返回404狀態碼
- 業務邏輯錯誤:通過
internalServerErrorResponse
處理業務邏輯錯誤,返回422狀態碼 - 系統內部錯誤:處理數據庫連接失敗、第三方服務異常等,返回500狀態碼
日志記錄機制
系統在關鍵節點記錄日志,便于問題排查和系統監控:
- 操作日志:記錄項目創建、更新、刪除等操作
- 錯誤日志:記錄錯誤信息和堆棧跟蹤
- 性能日志:記錄關鍵操作的執行時間
- 安全審計日志:記錄權限驗證和敏感操作
操作日志示例:
// 應用創建成功日志
logs.CtxInfof(ctx, "DraftAPPCreate success, userID=%d, spaceID=%d, appID=%d, appName=%s", userID, req.SpaceID, app.ID, app.Name)// 應用發布成功日志
logs.CtxInfof(ctx, "PublishAPP success, userID=%d, appID=%d, version=%s, publishRecordID=%d", userID, req.APPID, req.Version, publishRecord.ID)
錯誤日志示例:
// 權限驗證失敗日志
logs.CtxErrorf(ctx, "validateAPPPermission failed, userID=%d, appID=%d, err=%v", userID, req.APPID, err)// 業務邏輯錯誤日志
logs.CtxErrorf(ctx, "appDomainSVC.CreateDraftAPP failed, userID=%d, spaceID=%d, err=%v", userID, req.SpaceID, err)// 數據打包錯誤日志
logs.CtxErrorf(ctx, "packIntelligenceData failed, appID=%d, err=%v", app.ID, err)
性能監控日志示例:
// 接口響應時間監控
start := time.Now()
defer func() {logs.CtxInfof(ctx, "GetDraftIntelligenceList completed, userID=%d, duration=%v, count=%d", userID, time.Since(start), len(result.List))
}()
日志特點:
- 上下文關聯:使用
CtxInfof
和CtxErrorf
記錄帶請求上下文的日志 - 結構化信息:包含用戶ID、應用ID、工作空間ID等關鍵業務標識
- 操作追蹤:記錄完整的操作鏈路,便于問題排查和性能分析
- 錯誤詳情:詳細記錄錯誤信息、參數和調用棧
- 業務監控:記錄關鍵業務指標,支持運營分析和系統監控
- 安全審計:記錄權限驗證、敏感操作等安全相關事件
日志記錄實現
文件位置:backend/api/handler/coze/intelligence_service.go
核心代碼:
func DraftProjectCreate(ctx context.Context, c *app.RequestContext) {var err errorvar req intelligence.DraftProjectCreateRequest// 記錄請求開始logs.CtxInfof(ctx, "DraftProjectCreate started, req=%v", req)err = c.BindAndValidate(&req)if err != nil {logs.CtxErrorf(ctx, "DraftProjectCreate bind failed, err=%v", err)invalidParamRequestResponse(c, err.Error())return}resp, err := appApplication.APPApplicationSVC.DraftProjectCreate(ctx, &req)if err != nil {logs.CtxErrorf(ctx, "APPApplicationSVC.DraftProjectCreate failed, err=%v", err)internalServerErrorResponse(ctx, c, err)return}// 記錄操作成功logs.CtxInfof(ctx, "DraftProjectCreate success, projectID=%d", resp.ProjectID)c.JSON(consts.StatusOK, resp)
}
日志機制特點:
- 上下文追蹤:使用
logs.CtxInfof
和logs.CtxErrorf
記錄帶上下文的日志 - 分級記錄:根據日志級別記錄不同重要程度的信息
- 結構化日志:使用結構化格式便于日志分析
- 敏感信息保護:避免在日志中記錄敏感信息
10. 草稿應用列表查詢流程圖
GET /api/intelligence/draft/list?page=1&size=10↓
[API網關層] GetDraftIntelligenceList - 參數綁定和驗證↓
[搜索服務層] SearchSVC.GetDraftIntelligenceList - 提取用戶ID,構建查詢條件↓
[領域服務層] AppService.SearchDraftAPPs - 執行分頁查詢↓
[數據庫] SELECT * FROM app WHERE owner_id=? AND publish_status=0 ORDER BY updated_at_ms DESC LIMIT ?↓
[項目打包器] packIntelligenceData - 聚合應用信息、用戶信息、資源信息↓
[響應返回] 返回打包后的應用列表
草稿應用列表查詢詳細處理流程
- 用戶進入項目開發頁面
- 前端發送GET請求到
/api/intelligence/draft/list
GetDraftIntelligenceList
處理器調用SearchSVC.GetDraftIntelligenceList
- 搜索服務根據
owner_id
和publish_status=0
查詢草稿應用列表 packIntelligenceData
聚合應用信息、用戶信息、插件、知識庫等資源信息- 返回完整的應用列表給前端
核心技術特點
1. 分層架構設計
- 職責清晰:每層專注于特定的技術關注點
- 松耦合:通過接口和依賴注入實現解耦
- 可測試性:每層都可以獨立進行單元測試
- 可擴展性:新功能可以在不影響其他層的情況下添加
2. 事件驅動架構
- 異步處理:通過事件總線實現異步操作
- 解耦合:事件發布者和訂閱者之間松耦合
- 可擴展性:可以輕松添加新的事件處理器
- 可靠性:事件處理失敗不影響主流程
3. 領域驅動設計
- 業務建模:通過領域實體和服務建模業務邏輯
- 業務語言:使用業務術語命名類和方法
- 業務規則:在領域層集中管理業務規則
- 業務完整性:確保業務操作的原子性和一致性
4. 安全性保障
- 身份驗證:每個請求都需要驗證用戶身份
- 權限控制:用戶只能操作自己的項目
- 數據驗證:對所有輸入進行嚴格驗證
- 操作審計:記錄所有重要操作的日志
5. 高性能設計
- 分頁查詢:支持高效的分頁查詢
- 緩存策略:在適當的地方使用緩存提升性能
- 異步處理:通過事件總線實現異步操作
- 數據庫優化:合理的索引設計和查詢優化
6. 可維護性
- 代碼結構清晰:分層架構使代碼結構清晰
- 依賴注入:便于測試和維護
- 錯誤處理:完善的錯誤處理機制
- 日志記錄:詳細的日志記錄便于問題排查
總結
Coze Studio的項目開發中的應用列表查詢功能展現了現代Web應用后端架構的最佳實踐:
- 清晰的分層架構:從API網關到數據存儲,每層職責明確
- 事件驅動的設計:通過事件總線實現系統解耦和異步處理
- 領域驅動的建模:以業務為中心的領域建模和服務設計
- 完善的安全機制:用戶身份驗證和權限控制
- 高效的查詢性能:優化的分頁查詢和數據聚合
- 可維護的代碼結構:依賴注入和接口抽象提高可測試性
這種架構設計不僅保證了系統的性能和安全性,也為后續的功能擴展和維護奠定了堅實的基礎。通過事件驅動架構,系統具備了良好的擴展性和可維護性,能夠適應快速變化的業務需求。