前言
本文将深入分析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网关到数据存储,每层职责明确
- 事件驱动的设计:通过事件总线实现系统解耦和异步处理
- 领域驱动的建模:以业务为中心的领域建模和服务设计
- 完善的安全机制:用户身份验证和权限控制
- 高效的查询性能:优化的分页查询和数据聚合
- 可维护的代码结构:依赖注入和接口抽象提高可测试性
这种架构设计不仅保证了系统的性能和安全性,也为后续的功能扩展和维护奠定了坚实的基础。通过事件驱动架构,系统具备了良好的扩展性和可维护性,能够适应快速变化的业务需求。