前言

本文将深入分析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中定义的所有项目开发相关接口统一暴露,包括:

文件作用:

  1. 服务聚合中心: 统一管理所有业务模块的服务接口定义
  2. 代码生成入口: 作为Hertz框架代码生成的主要入口文件
  3. 接口统一暴露: 将分散在各个模块的接口统一暴露给客户端
  4. 依赖管理: 通过include语句管理各模块间的依赖关系
  5. 命名空间管理: 统一设置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)
}

实现功能

  1. 项目列表获取:获取用户的草稿智能体列表,支持分页和搜索

路由注册实现-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
}

文件作用:

  1. 中间件函数定义:为智能体项目开发模块的每个路由组和特定路由提供中间件挂载点
  2. 路由层级管理:按照路由的层级结构组织中间件函数,支持三层中间件架构
  3. 开发者扩展接口:提供统一的接口供开发者添加自定义中间件逻辑,如认证、鉴权、限流、日志记录等
  4. 粒度化控制:支持从模块级别到接口级别的细粒度中间件控制

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.gobackend/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)
}

服务初始化特点

  1. 依赖注入:通过ServiceComponents结构体注入15个不同的领域服务,实现完整的业务功能支持
  2. Elasticsearch集成:使用ES客户端提供强大的全文搜索和索引功能
  3. 事件驱动架构:集成项目和资源事件总线,支持异步事件处理和数据同步
  4. 消息队列消费者:自动注册项目和资源的MQ消费者,实现实时数据更新
  5. 多领域服务协调:整合智能体、APP、知识库、插件、工作流等多个领域服务
  6. 存储服务集成:支持数据库持久化、缓存加速和对象存储

应用搜索服务核心实现

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 结构

主要处理步骤

  1. 基础信息构建
  • 创建 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)}

领域服务特点

  1. 业务抽象:定义了智能体应用开发的核心业务操作,包括CRUD和发布管理
  2. 状态管理:管理应用从草稿到发布的状态转换和生命周期
  3. 版本控制:支持应用的版本管理和发布历史记录
  4. 权限控制:通过OwnerID确保用户只能操作自己的应用
  5. 资源管理:管理应用相关的资源,如图标、配置等
  6. 连接器集成:支持应用发布时的连接器配置和管理

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"` // 发布时间(毫秒)
}

实体设计特点

  1. 状态管理:通过PublishStatus字段管理草稿、发布成功、发布失败等状态
  2. 工作空间隔离:通过SpaceID实现多工作空间的数据隔离
  3. 权限控制:通过OwnerID确保应用的所有权管理
  4. 版本管理:支持应用的版本号和发布记录管理
  5. 连接器集成:通过ConnectorIDs字段管理应用的连接器配置
  6. 时间追踪:精确到毫秒的创建、更新、发布时间记录
  7. 扩展信息:通过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接口功能分析

  1. 接口职责:根据应用ID获取草稿状态的智能体应用信息
  2. 参数验证:接收上下文和应用ID作为参数
  3. 数据查询:通过仓储层调用DAO层查询草稿应用数据
  4. 存在性检查:验证应用是否存在,不存在时返回特定错误
  5. 错误处理:统一的错误处理和包装机制
  6. 返回结果:返回完整的APP实体对象

设计特点

  1. 分层架构:严格遵循领域驱动设计,服务层调用仓储层
  2. 错误处理:使用统一的错误包装机制,提供清晰的错误信息
  3. 数据完整性:确保返回的应用数据完整且有效
  4. 接口简洁:接口设计简洁明了,职责单一
  5. 类型安全:使用强类型参数和返回值,确保类型安全
数据访问(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
}

代码功能:

  1. 数据库查询 - 使用GORM查询框架,从 AppDraft 表中根据ID查找记录
  2. 错误处理 - 区分处理两种情况:
    • 记录不存在:返回 (nil, false, nil)
    • 其他数据库错误:返回 (nil, false, err)
  3. 数据转换 - 将数据库持久化对象(PO)转换为领域实体(DO)
    • appDraftPO(*res).ToDO() 执行PO到DO的转换
  4. 成功返回 - 找到记录时返回 (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
}

代码作用:

  1. 初始化管理
  • SetDefault() : 设置全局默认查询实例
  • Use() : 创建新的查询实例,初始化各表的查询器
  1. 数据库连接管理
  • Available() : 检查数据库连接是否可用
  • clone() : 克隆查询实例
  • ReadDB() / WriteDB() : 支持读写分离
  • ReplaceDB() : 替换数据库连接
  1. 上下文支持
  • WithContext() : 为查询操作添加上下文,返回带接口的查询上下文
  1. 事务管理
  • 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 接口:包含 SearchCreateUpdateDeleteCreateIndex 等方法
    • Types 接口:定义属性类型创建方法
    • BulkIndexer 接口:批量操作接口
  • model.go: 定义数据模型

    • Request:搜索请求结构体,包含查询条件、分页、排序等
    • Response:搜索响应结构体,包含命中结果和元数据
    • Hit:单个搜索结果
    • BulkIndexerItem:批量操作项
  • query.go: 定义查询相关结构

    • Query:查询结构体,支持多种查询类型
    • QueryType 常量:equalmatchmulti_matchnot_existscontainsin
    • BoolQuery:布尔查询,支持 mustshouldfiltermust_not
    • 各种查询构造函数:NewEqualQueryNewMatchQuery
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
查询执行流程
  1. 业务层调用backend/domain/search/service/search.go 中的 SearchProjects 方法
  2. 构建查询:创建 es.Request 对象,设置查询条件、排序、分页等
  3. 执行查询:调用 s.esClient.Search(ctx, projectIndexName, searchReq)
  4. 版本适配:根据 ES_VERSION 环境变量,自动选择 ES7 或 ES8 实现
  5. 查询转换
    • ES7:将抽象查询转换为 JSON 格式
    • ES8:将抽象查询转换为类型化结构体
  6. 结果处理:将 ES 响应转换为统一的 Response 结构体
索引使用:
  • 项目索引projectIndexName = "project_draft" 存储项目草稿信息
  • 资源索引resourceIndexName = "coze_resource" 存储各类资源信息
设计优势
  1. 版本兼容:同时支持 ES7 和 ES8,通过环境变量切换
  2. 接口统一:业务代码无需关心具体 ES 版本
  3. 类型安全:ES8 使用类型化 API,减少运行时错误
  4. 查询抽象:提供统一的查询构建方式,支持复杂的布尔查询
  5. 易于扩展:新增查询类型只需在 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=是"}}}
}

字段说明:
该索引主要用于存储项目草稿的元数据信息,支持以下功能:

  1. 基础信息:项目ID、名称、类型、状态
  2. 权限管理:工作空间ID、所有者ID
  3. 时间追踪:创建时间、更新时间、发布时间、最近打开时间、收藏时间
  4. 状态标记:发布状态、收藏状态、最近打开状态
  5. 搜索支持:项目名称支持全文搜索和精确匹配

该索引与 MySQL 中的 app_draft 表类似,但专门用于 ElasticSearch 的高效搜索和查询功能,支持复杂的搜索条件、排序和分页操作。

8. 安全和权限验证机制

用户身份验证流程

在项目开发功能中,系统需要验证用户身份以确保数据安全。整个身份验证流程如下:

  1. 会话验证:通过 ctxutil.GetUIDFromCtx(ctx) 从请求上下文中提取用户ID
  2. 工作空间隔离:确保用户只能访问所属工作空间的应用
  3. 所有者权限验证:验证用户对特定应用的所有权和操作权限
  4. 资源权限控制:验证用户对应用关联资源的访问权限

权限验证实现

文件位置: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
}

安全机制特点

  1. 身份验证:每个请求都需要验证用户身份
  2. 权限隔离:用户只能操作自己的项目
  3. 操作审计:记录所有项目操作的日志
  4. 数据验证:对输入参数进行严格验证

9. 错误处理和日志记录

错误处理机制

在项目开发功能中,系统采用了分层错误处理机制:

  1. 参数验证错误:通过 invalidParamRequestResponse 处理参数绑定和验证错误,返回400状态码
  2. 权限验证错误:专门处理权限验证失败的错误,返回403状态码
  3. 资源不存在错误:处理项目不存在等资源错误,返回404状态码
  4. 业务逻辑错误:通过 internalServerErrorResponse 处理业务逻辑错误,返回422状态码
  5. 系统内部错误:处理数据库连接失败、第三方服务异常等,返回500状态码

日志记录机制

系统在关键节点记录日志,便于问题排查和系统监控:

  1. 操作日志:记录项目创建、更新、删除等操作
  2. 错误日志:记录错误信息和堆栈跟踪
  3. 性能日志:记录关键操作的执行时间
  4. 安全审计日志:记录权限验证和敏感操作

操作日志示例

// 应用创建成功日志
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))
}()

日志特点

  1. 上下文关联:使用 CtxInfofCtxErrorf 记录带请求上下文的日志
  2. 结构化信息:包含用户ID、应用ID、工作空间ID等关键业务标识
  3. 操作追踪:记录完整的操作链路,便于问题排查和性能分析
  4. 错误详情:详细记录错误信息、参数和调用栈
  5. 业务监控:记录关键业务指标,支持运营分析和系统监控
  6. 安全审计:记录权限验证、敏感操作等安全相关事件

日志记录实现

文件位置: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)
}

日志机制特点

  1. 上下文追踪:使用 logs.CtxInfoflogs.CtxErrorf 记录带上下文的日志
  2. 分级记录:根据日志级别记录不同重要程度的信息
  3. 结构化日志:使用结构化格式便于日志分析
  4. 敏感信息保护:避免在日志中记录敏感信息

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_idpublish_status=0 查询草稿应用列表
  • packIntelligenceData 聚合应用信息、用户信息、插件、知识库等资源信息
  • 返回完整的应用列表给前端

核心技术特点

1. 分层架构设计

  • 职责清晰:每层专注于特定的技术关注点
  • 松耦合:通过接口和依赖注入实现解耦
  • 可测试性:每层都可以独立进行单元测试
  • 可扩展性:新功能可以在不影响其他层的情况下添加

2. 事件驱动架构

  • 异步处理:通过事件总线实现异步操作
  • 解耦合:事件发布者和订阅者之间松耦合
  • 可扩展性:可以轻松添加新的事件处理器
  • 可靠性:事件处理失败不影响主流程

3. 领域驱动设计

  • 业务建模:通过领域实体和服务建模业务逻辑
  • 业务语言:使用业务术语命名类和方法
  • 业务规则:在领域层集中管理业务规则
  • 业务完整性:确保业务操作的原子性和一致性

4. 安全性保障

  • 身份验证:每个请求都需要验证用户身份
  • 权限控制:用户只能操作自己的项目
  • 数据验证:对所有输入进行严格验证
  • 操作审计:记录所有重要操作的日志

5. 高性能设计

  • 分页查询:支持高效的分页查询
  • 缓存策略:在适当的地方使用缓存提升性能
  • 异步处理:通过事件总线实现异步操作
  • 数据库优化:合理的索引设计和查询优化

6. 可维护性

  • 代码结构清晰:分层架构使代码结构清晰
  • 依赖注入:便于测试和维护
  • 错误处理:完善的错误处理机制
  • 日志记录:详细的日志记录便于问题排查

总结

Coze Studio的项目开发中的应用列表查询功能展现了现代Web应用后端架构的最佳实践:

  1. 清晰的分层架构:从API网关到数据存储,每层职责明确
  2. 事件驱动的设计:通过事件总线实现系统解耦和异步处理
  3. 领域驱动的建模:以业务为中心的领域建模和服务设计
  4. 完善的安全机制:用户身份验证和权限控制
  5. 高效的查询性能:优化的分页查询和数据聚合
  6. 可维护的代码结构:依赖注入和接口抽象提高可测试性

这种架构设计不仅保证了系统的性能和安全性,也为后续的功能扩展和维护奠定了坚实的基础。通过事件驱动架构,系统具备了良好的扩展性和可维护性,能够适应快速变化的业务需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920880.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920880.shtml
英文地址,请注明出处:http://en.pswp.cn/news/920880.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机元件学习

DS18B20温度传感器51&#xff08;stc8h8k64u&#xff09;简介ds18B20是使用单总线的元器件代码/*--------------------------------------------------------------------- */ /* ------------------------ For STC8H MCU ----------------------------- */ /* --- Web: www.…

Spring事务管理策略对比与性能优化实践指南

Spring事务管理策略对比与性能优化实践指南 问题背景介绍 在现代企业级应用中&#xff0c;事务管理是保障数据一致性与安全性的核心机制。Spring作为主流的Java企业级开发框架&#xff0c;提供了多种事务管理方案&#xff0c;包括编程式事务、声明式事务以及与第三方分布式事务…

C++“类吸血鬼幸存者”游戏制作的要点学习

古之学者必有师&#xff0c;对于技术的提升&#xff0c;只靠自己的摸索虽然能得到深刻的经验&#xff0c;但往往没有较高的效率。笔者这些天学习了BV1eM4m1S74K“提瓦特幸存者”的C开发&#xff0c;也是实现了该类型游戏的开发。今天&#xff0c;就通过经验总结&#xff0c;亲手…

Python OpenCV图像处理与深度学习:Python OpenCV图像分割入门

图像分割&#xff1a;从基础到实践 学习目标 通过本课程&#xff0c;学员们将了解图像分割的基本概念&#xff0c;掌握使用OpenCV实现图像分割的方法&#xff0c;包括基于阈值的分割和基于区域的分割技术。同时&#xff0c;学员将能够独立完成简单的图像分割任务&#xff0c;并…

MQ使用场景分析

异步解耦‌系统间通过消息队列通信&#xff0c;降低耦合度&#xff08;如订单系统与库存系统&#xff09;典型场景&#xff1a;电商下单后异步通知物流系统‌流量削峰‌应对突发流量&#xff0c;将请求暂存到消息队列逐步处理典型场景&#xff1a;秒杀活动时缓冲高并发请求‌数…

人工智能学习:NLP文本处理的基本方法

一、分词 1、分词介绍 概念 分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符。分词过程就是找到这样分界符的过程…

Vue3 中 Proxy 在组件封装中的妙用

目录 Vue3 中 Proxy 在组件封装中的妙用&#xff1a;让组件交互更优雅 组件封装中的常见痛点 Proxy 是什么&#xff1f; Proxy 在组件封装中的应用 基础组件结构 使用 Proxy 实现方法透传 代码解析 父组件中的使用方式 Proxy 的其他应用场景 1. 权限控制 2. 方法调用…

DevExpress WinForms中文教程:Data Grid - 过滤编辑器

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

华为云CCE

华为云CCE&#xff1a;重构云原生应用的全栈引擎 一、云原生时代的"操作系统" 在数字经济浪潮中&#xff0c;容器化和微服务架构已成为企业数字化转型的标配。华为云容器引擎&#xff08;CCE&#xff09;作为云原生领域的"操作系统"&#xff0c;通过深度…

STM32——Uinx时间戳+BKP+RTC实时时钟

目录 一、Uinx时间戳 1.1Uinx简介 1.2UTC/GMT 1.3时间戳转换 1.3.1主要数据类型 1.3.2主要函数 1.3.3C语言时间戳转换示例 1.3.4时间格式化说明符 1.3.5注意事项 二、BKP 2.1BKP简介 2.2BKP基本结构 三、RTC 3.1RTC简介 3.2RTC框图 3.3RTC基本结构 3.4RTC硬件…

Java设计模式是什么?核心设计原则有哪些?

文章目录什么是设计模式&#xff1f;为什么使用设计模式&#xff1f;设计模式的核心设计原则是什么&#xff1f;1. 开闭原则&#xff08;Open-Closed Principle, OCP&#xff09;2. 里氏替换原则&#xff08;Liskov Substitution Principle, LSP&#xff09;3. 依赖倒置原则&am…

网络层和数据链路层

目录 1.网络层 2.数据链路层 1.网络层 我们知道&#xff0c;我们的消息为了从A端发送到B端&#xff0c;达成远距离传输&#xff0c;我们为此设计了很多协议层&#xff0c;分别是应用层&#xff0c;传输层&#xff0c;网络层&#xff0c;数据链路层&#xff0c;网卡&#xff0c…

Redis 的字典:像智能文件柜一样高效的哈希表实现

目录 一、从传统查找的痛点到哈希表的优势​ 二、哈希表的核心结构&#xff1a;文件柜的构成​ 2.1、 dictht 结构体&#xff1a;文件柜本体​ 2.2、dictEntry 结构体&#xff1a;带链条的文件夹​ 2.2.1、 哈希冲突的解决&#xff1a;抽屉里的链条​ 2.3、字典的高层封装…

FAST API部署和使用

第一部分&#xff1a;FastAPI 的使用&#xff08;开发环境&#xff09; 1. 安装 首先&#xff0c;你需要安装 FastAPI 和一个 ASGI 服务器&#xff0c;最常用的是 Uvicorn。 pip install "fastapi[standard]"这个命令会安装 FastAPI 以及所有推荐的依赖&#xff0c;包…

【JavaWeb】之HTML(对HTML细节的一些总结)

大家天天开心&#xff01; 文章目录 前言一、HTML的简介二、HTML运行方式三、html 的标签/元素-说明四、表单注意事项总结 前言 首先我们在把Java基础学习完之后&#xff0c;我们就要进行网站方面的开发了&#xff0c;我们要了解网页的组成&#xff0c;而网页的组成有HTML,CSS,…

互联网医院品牌IP的用户体验和生态构建

一、患者体验与信任构建互联网医院品牌IP的价值核心在于获得患者的深度信任&#xff0c;而卓越的用户体验是实现这一目标的关键路径。在医疗服务同质化严重的当下&#xff0c;患者体验已成为医疗机构差异化竞争的重要维度。研究表明&#xff0c;良好的用户体验能够提高用户满意…

【Node.js教程】Express框架入门:从搭建到动态渲染商品列表

前言 Visual Studio Code(简称VSCode)是微软开发的一款免费开源跨平台代码编辑器,凭借其免费、开源、跨平台的特性,以及丰富的插件生态和美观的界面,成为前端开发者的首选工具。 本文将带你从零开始学习Express框架,包括搭建项目、配置路由、使用中间件以及实现动态渲染…

众擎机器人开源代码解读

一&#xff0c;综述 EngineAI ROS 包&#xff1a; 高层开发模式&#xff1a;用户可通过发布身体速度指令&#xff0c;直接调用 EngineAI 机器人的行走控制器。底层开发模式&#xff1a;用户可通过发布关节指令&#xff0c;自主开发专属的控制器。 ROS2 package&#xff1a;全…

Windows系统安装Git详细教程

文章目录步骤 1&#xff1a;下载 Git 安装包步骤 2&#xff1a;运行安装程序步骤 3&#xff1a;选择安装路径步骤 4&#xff1a;选择组件步骤 5&#xff1a;选择默认编辑器步骤 6&#xff1a;选择路径环境变量步骤 7&#xff1a;选择 HTTPS 协议的传输方式步骤 8&#xff1a;配…

leetcode 3446. 按对角线进行矩阵排序 中等

给你一个大小为 n x n 的整数方阵 grid。返回一个经过如下调整的矩阵&#xff1a;左下角三角形&#xff08;包括中间对角线&#xff09;的对角线按 非递增顺序 排序。右上角三角形 的对角线按 非递减顺序 排序。示例 1&#xff1a;输入&#xff1a; grid [[1,7,3],[9,8,2],[4,…