前言

本文将深入分析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.base

struct 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.common

struct 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 nickname
    3: string avatar_url, // user avatar
    4: string user_unique_name, // user name
    5: 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.common

enum IntelligenceStatus {
    Using = 1,
    Deleted = 2,
    Banned = 3,
    MoveFailed = 4, // Migration failed

    Copying    = 5, // Copying
    CopyFailed = 6, // Copy failed
}

enum IntelligenceType {
    Bot = 1
    Project = 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 error
	var req intelligence.GetDraftIntelligenceListRequest
	err = 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 = searchDomainSVC
	SearchSVC.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.ResourceEventBus
	ProjectEventBus  = 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 + 1
			data := 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] = info
				return 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] = info
		lock.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.desc
	intelligenceData.BasicInfo.IconURI = projInfo.iconURI
	intelligenceData.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 service

import (
	"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 service

import (
	"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 *searchImpl

func 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.Hits

	hasMore := 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"`           // 工作空间ID
	IconURI           string `gorm:"column:icon_uri" json:"icon_uri"`           // 应用图标URI
	Name              string `gorm:"column:name" json:"name"`                   // 应用名称
	Desc              string `gorm:"column:desc" json:"desc"`                   // 应用描述
	OwnerID           int64  `gorm:"column:owner_id" json:"owner_id"`           // 应用所有者ID
	ConnectorIDs      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"` // 发布记录ID
	PublishStatus     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 singleagent

import (
	"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 singleagent

type SingleAgent struct {
	AgentID   int64
	CreatorID int64
	SpaceID   int64
	Name      string
	Desc      string
	IconURI   string
	CreatedAt int64
	UpdatedAt int64
	Version   string
	DeletedAt gorm.DeletedAt

	VariablesMetaID         *int64
	OnboardingInfo          *bot_common.OnboardingInfo
	ModelInfo               *bot_common.ModelInfo
	Prompt                  *bot_common.PromptInfo
	Plugin                  []*bot_common.PluginInfo
	Knowledge               *bot_common.Knowledge
	Workflow                []*bot_common.WorkflowInfo
	SuggestReply            *bot_common.SuggestReplyInfo
	JumpConfig              *bot_common.JumpConfig
	BackgroundImageInfoList []*bot_common.BackgroundImageInfo
	Database                []*bot_common.Database
	BotMode                 bot_common.BotMode
	LayoutInfo              *bot_common.LayoutInfo
	ShortcutCommand         []string
}

type AgentIdentity struct {
	AgentID int64
	// State   AgentState
	Version     string
	IsDraft     bool
	ConnectorID 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.AppDraft
	res, 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 repository

import (
	"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.IDGenerator
	dbQuery     *query.Query
	cacheClient 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.SingleAgentDraft
	singleAgent, 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 query

import (
	"context"
	"database/sql"

	"gorm.io/gorm"

	"gorm.io/gen"

	"gorm.io/plugin/dbresolver"
)

var (
	Q                      = new(Query)
	AppConnectorReleaseRef *appConnectorReleaseRef
	AppDraft               *appDraft
	AppReleaseRecord       *appReleaseRecord
)

func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
	*Q = *Use(db, opts...)
	AppConnectorReleaseRef = &Q.AppConnectorReleaseRef
	AppDraft = &Q.AppDraft
	AppReleaseRecord = &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.DB

	AppConnectorReleaseRef appConnectorReleaseRef
	AppDraft               appDraft
	AppReleaseRecord       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 query

import (
	"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 {
	appDraftDo

	ALL         field.Asterisk
	ID          field.Int64  // APP ID
	SpaceID     field.Int64  // Space ID
	OwnerID     field.Int64  // Owner ID
	IconURI     field.String // Icon URI
	Name        field.String // Application Name
	Description field.String // Application Description
	CreatedAt   field.Int64  // Create Time in Milliseconds
	UpdatedAt   field.Int64  // Update Time in Milliseconds
	DeletedAt   field.Field  // Delete Time

	fieldMap map[string]field.Expr
}

type IAppDraftDo interface {
	gen.SubQuery
	Debug() IAppDraftDo
	WithContext(ctx context.Context) IAppDraftDo
	WithResult(fc func(tx gen.Dao)) gen.ResultInfo
	ReplaceDB(db *gorm.DB)
	ReadDB() IAppDraftDo
	WriteDB() IAppDraftDo
	As(alias string) gen.Dao
	Session(config *gorm.Session) IAppDraftDo
	Columns(cols ...field.Expr) gen.Columns
	Clauses(conds ...clause.Expression) IAppDraftDo
	Not(conds ...gen.Condition) IAppDraftDo
	Or(conds ...gen.Condition) IAppDraftDo
	Select(conds ...field.Expr) IAppDraftDo
	Where(conds ...gen.Condition) IAppDraftDo
	Order(conds ...field.Expr) IAppDraftDo
	Distinct(cols ...field.Expr) IAppDraftDo
	Omit(cols ...field.Expr) IAppDraftDo
	Join(table schema.Tabler, on ...field.Expr) IAppDraftDo
	LeftJoin(table schema.Tabler, on ...field.Expr) IAppDraftDo
	RightJoin(table schema.Tabler, on ...field.Expr) IAppDraftDo
	Group(cols ...field.Expr) IAppDraftDo
	Having(conds ...gen.Condition) IAppDraftDo
	Limit(limit int) IAppDraftDo
	Offset(offset int) IAppDraftDo
	Count() (count int64, err error)
	Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDraftDo
	Unscoped() IAppDraftDo
	Create(values ...*model.AppDraft) error
	CreateInBatches(values []*model.AppDraft, batchSize int) error
	Save(values ...*model.AppDraft) error
	First() (*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) error
	Pluck(column field.Expr, dest interface{}) error
	Delete(...*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.Dao
	Attrs(attrs ...field.AssignExpr) IAppDraftDo
	Assign(attrs ...field.AssignExpr) IAppDraftDo
	Joins(fields ...field.RelationField) IAppDraftDo
	Preload(fields ...field.RelationField) IAppDraftDo
	FirstOrInit() (*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) IAppDraftDo
	UnderlyingDB() *gorm.DB
	schema.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 model

import (
	"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 ID
	SpaceID     int64          `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"`                                             // Space ID
	OwnerID     int64          `gorm:"column:owner_id;not null;comment:Owner ID" json:"owner_id"`                                             // Owner ID
	IconURI     string         `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"`                                             // Icon URI
	Name        string         `gorm:"column:name;not null;comment:Application Name" json:"name"`                                             // Application Name
	Description string         `gorm:"column:description;comment:Application Description" json:"description"`                                 // Application Description
	CreatedAt   int64          `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
	UpdatedAt   int64          `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds
	DeletedAt   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 query

import (
	"context"
	"database/sql"

	"gorm.io/gorm"

	"gorm.io/gen"

	"gorm.io/plugin/dbresolver"
)

var (
	Q                  = new(Query)
	SingleAgentDraft   *singleAgentDraft
	SingleAgentPublish *singleAgentPublish
	SingleAgentVersion *singleAgentVersion
)

func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
	*Q = *Use(db, opts...)
	SingleAgentDraft = &Q.SingleAgentDraft
	SingleAgentPublish = &Q.SingleAgentPublish
	SingleAgentVersion = &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.DB

	SingleAgentDraft   singleAgentDraft
	SingleAgentPublish singleAgentPublish
	SingleAgentVersion singleAgentVersion
}

SingleAgentRepo-single_agent_draft数据查询

文件位置:backend\domain\agent\singleagent\internal\dal\query\single_agent_draft.gen.go

package query

import (
	"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 {
	singleAgentDraftDo

	ALL                     field.Asterisk
	ID                      field.Int64  // Primary Key ID
	AgentID                 field.Int64  // Agent ID
	CreatorID               field.Int64  // Creator ID
	SpaceID                 field.Int64  // Space ID
	Name                    field.String // Agent Name
	Description             field.String // Agent Description
	IconURI                 field.String // Icon URI
	CreatedAt               field.Int64  // Create Time in Milliseconds
	UpdatedAt               field.Int64  // Update Time in Milliseconds
	DeletedAt               field.Field  // delete time in millisecond
	VariablesMetaID         field.Int64  // variables meta table ID
	ModelInfo               field.Field  // Model Configuration Information
	OnboardingInfo          field.Field  // Onboarding Information
	Prompt                  field.Field  // Agent Prompt Configuration
	Plugin                  field.Field  // Agent Plugin Base Configuration
	Knowledge               field.Field  // Agent Knowledge Base Configuration
	Workflow                field.Field  // Agent Workflow Configuration
	SuggestReply            field.Field  // Suggested Replies
	JumpConfig              field.Field  // Jump Configuration
	BackgroundImageInfoList field.Field  // Background image
	DatabaseConfig          field.Field  // Agent Database Base Configuration
	BotMode                 field.Int32  // mod,0:single mode 2:chatflow mode
	ShortcutCommand         field.Field  // shortcut command
	LayoutInfo              field.Field  // chatflow layout info

	fieldMap 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.ID
	s.fieldMap["agent_id"] = s.AgentID
	s.fieldMap["creator_id"] = s.CreatorID
	s.fieldMap["space_id"] = s.SpaceID
	s.fieldMap["name"] = s.Name
	s.fieldMap["description"] = s.Description
	s.fieldMap["icon_uri"] = s.IconURI
	s.fieldMap["created_at"] = s.CreatedAt
	s.fieldMap["updated_at"] = s.UpdatedAt
	s.fieldMap["deleted_at"] = s.DeletedAt
	s.fieldMap["variables_meta_id"] = s.VariablesMetaID
	s.fieldMap["model_info"] = s.ModelInfo
	s.fieldMap["onboarding_info"] = s.OnboardingInfo
	s.fieldMap["prompt"] = s.Prompt
	s.fieldMap["plugin"] = s.Plugin
	s.fieldMap["knowledge"] = s.Knowledge
	s.fieldMap["workflow"] = s.Workflow
	s.fieldMap["suggest_reply"] = s.SuggestReply
	s.fieldMap["jump_config"] = s.JumpConfig
	s.fieldMap["background_image_info_list"] = s.BackgroundImageInfoList
	s.fieldMap["database_config"] = s.DatabaseConfig
	s.fieldMap["bot_mode"] = s.BotMode
	s.fieldMap["shortcut_command"] = s.ShortcutCommand
	s.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.SubQuery
	Debug() ISingleAgentDraftDo
	WithContext(ctx context.Context) ISingleAgentDraftDo
	WithResult(fc func(tx gen.Dao)) gen.ResultInfo
	ReplaceDB(db *gorm.DB)
	ReadDB() ISingleAgentDraftDo
	WriteDB() ISingleAgentDraftDo
	As(alias string) gen.Dao
	Session(config *gorm.Session) ISingleAgentDraftDo
	Columns(cols ...field.Expr) gen.Columns
	Clauses(conds ...clause.Expression) ISingleAgentDraftDo
	Not(conds ...gen.Condition) ISingleAgentDraftDo
	Or(conds ...gen.Condition) ISingleAgentDraftDo
	Select(conds ...field.Expr) ISingleAgentDraftDo
	Where(conds ...gen.Condition) ISingleAgentDraftDo
	Order(conds ...field.Expr) ISingleAgentDraftDo
	Distinct(cols ...field.Expr) ISingleAgentDraftDo
	Omit(cols ...field.Expr) ISingleAgentDraftDo
	Join(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDo
	LeftJoin(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDo
	RightJoin(table schema.Tabler, on ...field.Expr) ISingleAgentDraftDo
	Group(cols ...field.Expr) ISingleAgentDraftDo
	Having(conds ...gen.Condition) ISingleAgentDraftDo
	Limit(limit int) ISingleAgentDraftDo
	Offset(offset int) ISingleAgentDraftDo
	Count() (count int64, err error)
	Scopes(funcs ...func(gen.Dao) gen.Dao) ISingleAgentDraftDo
	Unscoped() ISingleAgentDraftDo
	Create(values ...*model.SingleAgentDraft) error
	CreateInBatches(values []*model.SingleAgentDraft, batchSize int) error
	Save(values ...*model.SingleAgentDraft) error
	First() (*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) error
	Pluck(column field.Expr, dest interface{}) error
	Delete(...*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.Dao
	Attrs(attrs ...field.AssignExpr) ISingleAgentDraftDo
	Assign(attrs ...field.AssignExpr) ISingleAgentDraftDo
	Joins(fields ...field.RelationField) ISingleAgentDraftDo
	Preload(fields ...field.RelationField) ISingleAgentDraftDo
	FirstOrInit() (*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) ISingleAgentDraftDo
	UnderlyingDB() *gorm.DB
	schema.Tabler
}

AppRepo-single_agent_draft数据模型

文件位置:backend\domain\agent\singleagent\internal\dal\model\single_agent_draft.gen.go

package model

import (
	"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 ID
	AgentID                 int64                             `gorm:"column:agent_id;not null;comment:Agent ID" json:"agent_id"`                                                    // Agent ID
	CreatorID               int64                             `gorm:"column:creator_id;not null;comment:Creator ID" json:"creator_id"`                                              // Creator ID
	SpaceID                 int64                             `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"`                                                    // Space ID
	Name                    string                            `gorm:"column:name;not null;comment:Agent Name" json:"name"`                                                          // Agent Name
	Description             string                            `gorm:"column:description;not null;comment:Agent Description" json:"description"`                                     // Agent Description
	IconURI                 string                            `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"`                                                    // Icon URI
	CreatedAt               int64                             `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"`        // Create Time in Milliseconds
	UpdatedAt               int64                             `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"`        // Update Time in Milliseconds
	DeletedAt               gorm.DeletedAt                    `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"`                                       // delete time in millisecond
	VariablesMetaID         *int64                            `gorm:"column:variables_meta_id;comment:variables meta table ID" json:"variables_meta_id"`                            // variables meta table ID
	ModelInfo               *bot_common.ModelInfo             `gorm:"column:model_info;comment:Model Configuration Information;serializer:json" json:"model_info"`                  // Model Configuration Information
	OnboardingInfo          *bot_common.OnboardingInfo        `gorm:"column:onboarding_info;comment:Onboarding Information;serializer:json" json:"onboarding_info"`                 // Onboarding Information
	Prompt                  *bot_common.PromptInfo            `gorm:"column:prompt;comment:Agent Prompt Configuration;serializer:json" json:"prompt"`                               // Agent Prompt Configuration
	Plugin                  []*bot_common.PluginInfo          `gorm:"column:plugin;comment:Agent Plugin Base Configuration;serializer:json" json:"plugin"`                          // Agent Plugin Base Configuration
	Knowledge               *bot_common.Knowledge             `gorm:"column:knowledge;comment:Agent Knowledge Base Configuration;serializer:json" json:"knowledge"`                 // Agent Knowledge Base Configuration
	Workflow                []*bot_common.WorkflowInfo        `gorm:"column:workflow;comment:Agent Workflow Configuration;serializer:json" json:"workflow"`                         // Agent Workflow Configuration
	SuggestReply            *bot_common.SuggestReplyInfo      `gorm:"column:suggest_reply;comment:Suggested Replies;serializer:json" json:"suggest_reply"`                          // Suggested Replies
	JumpConfig              *bot_common.JumpConfig            `gorm:"column:jump_config;comment:Jump Configuration;serializer:json" json:"jump_config"`                             // Jump Configuration
	BackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background image
	DatabaseConfig          []*bot_common.Database            `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"`      // Agent Database Base Configuration
	BotMode                 int32                             `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"`                           // mod,0:single mode 2:chatflow mode
	ShortcutCommand         []string                          `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"`                     // shortcut command
	LayoutInfo              *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 orm

import (
	"gorm.io/gorm"
)

type DB = gorm.DB

文件作用:

  • 定义了 type DB = gorm.DB ,为 GORM 数据库对象提供类型别名
  • 作为契约层(Contract),为上层提供统一的数据库接口抽象
  • 便于后续可能的数据库实现替换(如从 MySQL 切换到 PostgreSQL)

mysql.go文件详解

文件位置:backend/infra/impl/mysql/mysql.go
核心代码:

package mysql

import (
	"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 error
	var 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. 可维护的代码结构:依赖注入和接口抽象提高可测试性

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

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐