3. 应用服务层

PluginApplicationService初始化

文件位置:backend/application/plugin/plugin.go

PluginApplicationService是插件应用服务层的核心组件,专门负责处理插件资源的创建等业务逻辑,是连接API层和领域层的重要桥梁。在用户点击"资源库" → 接着点击右上角的"+"号 → 最后点击弹出菜单中的"插件"的场景中,该服务承担着核心的创建业务处理职责。

服务结构定义

文件位置:backend/application/plugin/plugin.go

type PluginApplicationService struct {
	DomainSVC service.PluginService
	toolRepo  repository.ToolRepository
	eventbus  search.ResourceEventBus
}

var PluginSVC = &PluginApplicationService{}
服务初始化实现

文件位置:backend/application/plugin/init.go

// InitService 初始化插件应用服务,注入领域服务依赖
func InitService(pluginRepo repository.PluginRepository, toolRepo repository.ToolRepository, eventbus ResourceEventBus) *PluginApplicationService {
	// 创建插件领域服务
	pluginDomainSVC := service.NewService(pluginRepo, toolRepo)

	// 初始化应用服务
	service := &PluginApplicationService{
		DomainSVC: pluginDomainSVC,
		toolRepo:  toolRepo,
		eventbus:  eventbus,
	}

	return service
}

服务初始化特点

  1. 依赖注入:通过Repository接口注入数据访问能力,实现依赖倒置
  2. 事件驱动架构:集成资源事件总线,支持异步事件处理和数据同步
  3. 领域服务协调:封装插件领域服务,提供应用层的业务编排
  4. 多仓储支持:同时注入插件和工具仓储,支持复杂的插件管理操作

创建插件核心实现

RegisterPluginMeta方法详解

文件位置:backend/application/plugin/plugin.go

当用户在资源库中点击右上角的"+"号,然后点击弹出菜单中的"插件"时,前端会调用RegisterPluginMeta方法来创建新的插件资源。

// 文件位置:backend/application/plugin/plugin.go
func (p *PluginApplicationService) RegisterPluginMeta(ctx context.Context, req *pluginAPI.RegisterPluginMetaRequest) (resp *pluginAPI.RegisterPluginMetaResponse, err error) {
	userID := ctxutil.GetUIDFromCtx(ctx)
	if userID == nil {
		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
	}

	// 验证授权类型
	_authType, ok := model.ToAuthType(req.GetAuthType())
	if !ok {
		return nil, fmt.Errorf("invalid auth type '%d'", req.GetAuthType())
	}
	authType := ptr.Of(_authType)

	// 验证子授权类型
	var authSubType *model.AuthzSubType
	if req.SubAuthType != nil {
		_authSubType, ok := model.ToAuthSubType(req.GetSubAuthType())
		if !ok {
			return nil, fmt.Errorf("invalid sub authz type '%d'", req.GetSubAuthType())
		}
		authSubType = ptr.Of(_authSubType)
	}

	// 验证参数位置
	var loc model.HTTPParamLocation
	if *authType == model.AuthzTypeOfService {
		if req.GetLocation() == common.AuthorizationServiceLocation_Query {
			loc = model.ParamInQuery
		} else if req.GetLocation() == common.AuthorizationServiceLocation_Header {
			loc = model.ParamInHeader
		} else {
			return nil, fmt.Errorf("invalid location '%s'", req.GetLocation())
		}
	}

	// 构建创建请求
	r := &service.CreateDraftPluginRequest{
		PluginType:   req.GetPluginType(),
		SpaceID:      req.GetSpaceID(),
		DeveloperID:  *userID,
		IconURI:      req.Icon.URI,
		ProjectID:    req.ProjectID,
		Name:         req.GetName(),
		Desc:         req.GetDesc(),
		ServerURL:    req.GetURL(),
		CommonParams: req.CommonParams,
		AuthInfo: &service.PluginAuthInfo{
			AuthzType:    authType,
			Location:     ptr.Of(loc),
			Key:          req.Key,
			ServiceToken: req.ServiceToken,
			OAuthInfo:    req.OauthInfo,
			AuthzSubType: authSubType,
			AuthzPayload: req.AuthPayload,
		},
	}
	
	// 调用领域服务创建插件
	pluginID, err := p.DomainSVC.CreateDraftPlugin(ctx, r)
	if err != nil {
		return nil, errorx.Wrapf(err, "CreateDraftPlugin failed")
	}

	// 发布创建事件
	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
		OpType: searchEntity.Created,
		Resource: &searchEntity.ResourceDocument{
			ResType:       resCommon.ResType_Plugin,
			ResSubType:    ptr.Of(int32(req.GetPluginType())),
			ResID:         pluginID,
			Name:          &req.Name,
			SpaceID:       &req.SpaceID,
			APPID:         req.ProjectID,
			OwnerID:       userID,
			PublishStatus: ptr.Of(resCommon.PublishStatus_UnPublished),
			CreateTimeMS:  ptr.Of(time.Now().UnixMilli()),
		},
	})
	if err != nil {
		return nil, fmt.Errorf("publish resource '%d' failed, err=%v", pluginID, err)
	}

	resp = &pluginAPI.RegisterPluginMetaResponse{
		PluginID: pluginID,
	}
	return resp, nil
}

方法功能特点

  1. 身份验证:验证用户登录状态,确保只有登录用户可以创建插件
  2. 参数验证:验证授权类型、插件类型等关键参数的有效性
  3. 领域服务调用:调用领域服务层的CreateDraftPlugin方法执行创建逻辑
  4. 事件发布:创建完成后发布创建事件,支持搜索索引更新等下游处理
  5. 错误处理:完善的错误处理机制,包装错误信息便于调试
  6. 时间戳记录:在事件中记录创建时间,便于审计和追踪
创建插件业务流程

创建插件的完整业务流程包括以下几个关键步骤:

  1. 身份验证:验证用户登录状态和创建权限
  2. 参数验证:验证插件名称、描述、授权类型等必要参数
  3. 数据构建:构建插件清单(Manifest)和OpenAPI文档
  4. 执行创建操作:调用领域服务执行实际的创建操作
  5. 事件发布:发布创建事件,通知其他系统组件进行相应处理
  6. 返回响应:返回创建操作的结果,包含新创建的插件ID

创建操作的安全性保障

  • 权限控制:严格的用户身份验证,确保只有授权用户可以创建插件
  • 数据验证:完整的参数验证机制,确保创建数据的有效性
  • 事件驱动:异步事件处理,确保相关数据的同步更新
  • 错误处理:完善的错误处理机制,确保系统稳定性
  • 资源隔离:通过SpaceID实现多租户资源隔离

4. 领域服务层

插件领域服务层架构

插件领域服务层是Coze Studio中处理插件业务逻辑的核心层,负责插件资源的创建、管理和业务规则实现。该层采用领域驱动设计(DDD)模式,将业务逻辑与数据访问分离,确保代码的可维护性和可扩展性。

插件领域服务接口定义

文件位置:backend/domain/plugin/service/service.go

插件领域服务接口定义了插件管理的核心业务能力,包括插件资源的完整生命周期管理。

type PluginService interface {
	// 创建草稿插件
	CreateDraftPlugin(ctx context.Context, req *CreateDraftPluginRequest) (pluginID int64, err error)
	CreateDraftPluginWithCode(ctx context.Context, req *CreateDraftPluginWithCodeRequest) (resp *CreateDraftPluginWithCodeResponse, err error)
	
	// 其他插件管理方法
	GetDraftPlugin(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error)
	UpdateDraftPlugin(ctx context.Context, req *UpdateDraftPluginRequest) error
	DeleteDraftPlugin(ctx context.Context, pluginID int64) error
}

核心接口功能

  1. 插件资源管理:创建、获取、更新、删除用户自定义的插件资源
  2. 创建操作核心:CreateDraftPlugin方法是创建插件的核心业务接口
  3. 草稿管理:专门处理草稿状态的插件,支持开发过程中的迭代
  4. 业务规则封装:封装插件相关的业务逻辑和验证规则
  5. 数据一致性:确保插件数据的完整性和一致性
  6. 多种创建方式:支持通过UI创建和通过代码创建两种方式
插件领域服务实现

文件位置:backend/domain/plugin/service/plugin_draft.go

插件服务实现类包含了所有插件相关业务逻辑的具体实现,依赖于仓储层进行数据持久化。

type pluginServiceImpl struct {
	pluginRepo repository.PluginRepository
	toolRepo   repository.ToolRepository
}

func NewService(pluginRepo repository.PluginRepository, toolRepo repository.ToolRepository) PluginService {
	return &pluginServiceImpl{
		pluginRepo: pluginRepo,
		toolRepo:   toolRepo,
	}
}

// CreateDraftPlugin 创建草稿插件
// 文件位置:backend/domain/plugin/service/plugin_draft.go
func (p *pluginServiceImpl) CreateDraftPlugin(ctx context.Context, req *CreateDraftPluginRequest) (pluginID int64, err error) {
	// 构建默认插件清单
	mf := entity.NewDefaultPluginManifest()
	mf.CommonParams = map[model.HTTPParamLocation][]*plugin_develop_common.CommonParamSchema{}
	mf.NameForHuman = req.Name
	mf.NameForModel = req.Name
	mf.DescriptionForHuman = req.Desc
	mf.DescriptionForModel = req.Desc
	mf.API.Type, _ = model.ToPluginType(req.PluginType)
	mf.LogoURL = req.IconURI

	// 设置授权信息
	authV2, err := req.AuthInfo.toAuthV2()
	if err != nil {
		return 0, err
	}
	mf.Auth = authV2

	// 处理公共参数
	for loc, params := range req.CommonParams {
		location, ok := model.ToHTTPParamLocation(loc)
		if !ok {
			return 0, fmt.Errorf("invalid location '%s'", loc.String())
		}
		for _, param := range params {
			mf.CommonParams[location] = append(mf.CommonParams[location],
				&plugin_develop_common.CommonParamSchema{
					Name:  param.Name,
					Value: param.Value,
				})
		}
	}

	// 构建OpenAPI文档
	doc := entity.NewDefaultOpenapiDoc()
	doc.Servers = append(doc.Servers, &openapi3.Server{
		URL: req.ServerURL,
	})
	doc.Info.Title = req.Name
	doc.Info.Description = req.Desc

	// 验证数据有效性
	err = doc.Validate(ctx)
	if err != nil {
		return 0, err
	}
	err = mf.Validate(false)
	if err != nil {
		return 0, err
	}

	// 创建插件实体
	pl := entity.NewPluginInfo(&model.PluginInfo{
		IconURI:     ptr.Of(req.IconURI),
		SpaceID:     req.SpaceID,
		ServerURL:   ptr.Of(req.ServerURL),
		DeveloperID: req.DeveloperID,
		APPID:       req.ProjectID,
		PluginType:  req.PluginType,
		Manifest:    mf,
		OpenapiDoc:  doc,
	})

	// 调用仓储层创建插件
	pluginID, err = p.pluginRepo.CreateDraftPlugin(ctx, pl)
	if err != nil {
		return 0, errorx.Wrapf(err, "CreateDraftPlugin failed")
	}

	return pluginID, nil
}

创建操作实现特点

  1. 依赖注入:通过Repository接口注入数据访问能力,实现松耦合
  2. 仓储模式:使用Repository模式进行数据访问抽象,隔离业务逻辑与数据层
  3. 数据构建:CreateDraftPlugin方法负责构建完整的插件数据结构
  4. 数据验证:完整的数据验证机制,确保创建数据的有效性
  5. 业务隔离:领域服务层专注于业务逻辑,数据操作委托给仓储层
  6. 多仓储协调:同时管理插件和工具仓储,支持复杂的插件创建操作

CreateDraftPlugin方法详解

  • 参数丰富:接收CreateDraftPluginRequest参数,包含插件创建所需的完整信息
  • 数据构建:构建插件清单(Manifest)和OpenAPI文档等核心数据结构
  • 验证机制:对构建的数据进行完整性和有效性验证
  • 错误处理:完善的错误处理和传播机制,确保创建异常的正确处理
  • 返回值:创建成功返回插件ID,失败返回具体错误信息
  • 业务纯净:不包含权限验证等应用层逻辑,专注于领域层的创建操作
插件实体定义

文件位置:backend/domain/plugin/entity/plugin.go

插件实体定义了插件资源的核心数据结构,包含了插件的所有关键属性。

type PluginInfo struct {
	ID          int64                    // 插件唯一标识
	PluginType  common.PluginType        // 插件类型(本地/远程)
	SpaceID     int64                    // 所属空间ID,支持多租户隔离
	DeveloperID int64                    // 开发者ID
	APPID       *int64                   // 关联应用ID
	IconURI     *string                  // 插件图标URI
	ServerURL   *string                  // 服务器URL
	Manifest    *model.PluginManifest    // 插件清单信息
	OpenapiDoc  *model.Openapi3T         // OpenAPI文档
	CreatedAt   int64                    // 创建时间戳
	UpdatedAt   int64                    // 更新时间戳
}

实体设计特点

  1. 基础信息:包含ID、类型、空间等基本属性,满足插件的基本信息需求
  2. 类型支持:PluginType字段支持本地和远程插件类型,灵活适配不同场景
  3. 多租户支持:SpaceID字段支持多租户和空间隔离,确保数据安全
  4. 开发者管理:DeveloperID字段支持开发者权限控制和资源归属管理
  5. 应用关联:APPID字段支持插件与应用的关联关系
  6. 配置管理:Manifest和OpenapiDoc字段存储插件的配置和API文档
  7. 时间追踪:CreatedAt和UpdatedAt支持创建和更新时间追踪,便于审计和版本管理

5. 数据访问层

5.1 仓储接口定义

插件仓储接口

文件位置:backend/domain/plugin/repository/plugin.go

type PluginRepository interface {
	// CreateDraftPlugin 创建插件草稿
	CreateDraftPlugin(ctx context.Context, plugin *entity.PluginInfo) (pluginID int64, err error)
	// CreateDraftPluginWithCode 通过代码创建插件草稿
	CreateDraftPluginWithCode(ctx context.Context, req *CreateDraftPluginWithCodeRequest) (resp *CreateDraftPluginWithCodeResponse, err error)
	// GetDraftPlugin 获取插件草稿
	GetDraftPlugin(ctx context.Context, pluginID int64, opt *dal.PluginSelectedOption) (*entity.PluginInfo, error)
	// 其他CRUD方法...
}

创建方法特点

  • 事务创建:CreateDraftPlugin方法支持事务操作,确保数据一致性
  • 多种创建方式:支持通过UI和代码两种方式创建插件
  • 上下文支持:支持context.Context进行请求链路追踪
  • 错误处理:返回error类型,便于上层进行错误处理
  • ID生成:自动生成唯一pluginID标识新创建的插件资源

5.2 数据访问对象(DAO)

插件创建的具体实现

文件位置:backend/domain/plugin/repository/plugin_impl.go

插件创建操作涉及数据验证、ID生成和数据插入,需要通过事务确保数据一致性:

func (p *pluginRepoImpl) CreateDraftPlugin(ctx context.Context, plugin *entity.PluginInfo) (pluginID int64, err error) {
	pluginID, err = p.pluginDraftDAO.Create(ctx, plugin)
	if err != nil {
		return 0, err
	}

	return pluginID, nil
}

func (p *pluginRepoImpl) CreateDraftPluginWithCode(ctx context.Context, req *CreateDraftPluginWithCodeRequest) (resp *CreateDraftPluginWithCodeResponse, err error) {
	tx := p.query.Begin()
	if tx.Error != nil {
		return nil, tx.Error
	}

	defer func() {
		if r := recover(); r != nil {
			if e := tx.Rollback(); e != nil {
				logs.CtxErrorf(ctx, "rollback failed, err=%v", e)
			}
			err = fmt.Errorf("catch panic: %v\nstack=%s", r, string(debug.Stack()))
			return
		}
		if err != nil {
			if e := tx.Rollback(); e != nil {
				logs.CtxErrorf(ctx, "rollback failed, err=%v", e)
			}
		}
	}()

	// 创建插件草稿数据
	pluginID, err := p.pluginDraftDAO.CreateWithTX(ctx, tx, plugin)
	if err != nil {
		return nil, err
	}

	// 创建相关工具数据
	tools, err := p.createToolsFromOpenAPI(ctx, tx, pluginID, req.OpenapiDoc)
	if err != nil {
		return nil, err
	}

	err = tx.Commit()
	if err != nil {
		return nil, err
	}

	return &CreateDraftPluginWithCodeResponse{
		Plugin: plugin,
		Tools:  tools,
	}, nil
}

创建操作的关键特点

  1. 事务保证:使用数据库事务确保所有创建操作的原子性
  2. ID生成:自动生成唯一的插件ID,避免ID冲突
  3. 异常处理:完善的panic恢复和事务回滚机制
  4. 数据完整性:确保创建操作的数据完整性和一致性
  5. 多表协调:协调创建插件相关的多个数据表

创建涉及的数据表

  • plugin_draft:插件草稿表
  • tool_draft:工具草稿表(如果通过代码创建)
  • plugin_oauth_auth:插件OAuth认证表(如果需要)
PluginDraftDAO结构体

文件位置:backend/domain/plugin/internal/dal/plugin_draft.go

PluginDraftDAO是插件草稿数据访问的具体实现,负责与数据库进行交互,处理插件草稿资源的持久化操作。

package dal

import (
	"context"
	"encoding/json"
	"fmt"

	"gorm.io/gen"
	"gorm.io/gorm"

	"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
	"github.com/coze-dev/coze-studio/backend/domain/plugin/internal/dal/model"
	"github.com/coze-dev/coze-studio/backend/domain/plugin/internal/dal/query"
	"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
	"github.com/coze-dev/coze-studio/backend/pkg/slices"
)

type PluginDraftDAO struct {
	query *query.Query
	idGen idgen.IDGenerator
}

func NewPluginDraftDAO(db *gorm.DB, generator idgen.IDGenerator) *PluginDraftDAO {
	return &PluginDraftDAO{
		query: query.Use(db),
		idGen: generator,
	}
}
插件创建操作实现

创建插件草稿

func (p *PluginDraftDAO) Create(ctx context.Context, plugin *entity.PluginInfo) (pluginID int64, err error) {
	id, err := p.genPluginID(ctx)
	if err != nil {
		return 0, err
	}

	mf, err := plugin.Manifest.EncryptAuthPayload()
	if err != nil {
		return 0, fmt.Errorf("EncryptAuthPayload failed, err=%w", err)
	}

	table := p.query.PluginDraft
	err = table.WithContext(ctx).Create(&model.PluginDraft{
		ID:          id,
		SpaceID:     plugin.SpaceID,
		DeveloperID: plugin.DeveloperID,
		PluginType:  int32(plugin.PluginType),
		IconURI:     plugin.GetIconURI(),
		ServerURL:   plugin.GetServerURL(),
		AppID:       plugin.GetAPPID(),
		Manifest:    mf,
		OpenapiDoc:  plugin.OpenapiDoc,
	})
	if err != nil {
		return 0, err
	}

	return id, nil
}
插件ID生成操作

文件位置:backend/domain/plugin/internal/dal/plugin_draft.go

生成唯一插件ID

func (p *PluginDraftDAO) genPluginID(ctx context.Context) (id int64, err error) {
	retryTimes := 5
	for i := 0; i < retryTimes; i++ {
		id, err = p.idGen.GenID(ctx)
		if err != nil {
			return 0, err
		}
		if _, ok := conf.GetPluginProduct(id); !ok {
			break
		}
		if i == retryTimes-1 {
			return 0, fmt.Errorf("id %d is confilict with product plugin id.", id)
		}
	}

	return id, nil
}
数据转换方法

DO到PO转换(Domain Object to Persistent Object)

func (p *PluginDraftDAO) pluginInfoDO2PO(plugin *entity.PluginInfo) *model.PluginDraft {
	mf, _ := plugin.Manifest.EncryptAuthPayload()
	return &model.PluginDraft{
		ID:          plugin.ID,
		SpaceID:     plugin.SpaceID,
		DeveloperID: plugin.DeveloperID,
		PluginType:  int32(plugin.PluginType),
		IconURI:     plugin.GetIconURI(),
		ServerURL:   plugin.GetServerURL(),
		AppID:       plugin.GetAPPID(),
		Manifest:    mf,
		OpenapiDoc:  plugin.OpenapiDoc,
	}
}

PO到DO转换(Persistent Object to Domain Object)

type pluginDraftPO model.PluginDraft

func (p pluginDraftPO) ToDO() *entity.PluginInfo {
	plugin := &entity.PluginInfo{
		ID:          p.ID,
		SpaceID:     p.SpaceID,
		DeveloperID: p.DeveloperID,
		PluginType:  common.PluginType(p.PluginType),
		IconURI:     &p.IconURI,
		ServerURL:   &p.ServerURL,
		APPID:       &p.AppID,
		Manifest:    p.Manifest,
		OpenapiDoc:  p.OpenapiDoc,
		CreatedAt:   p.CreatedAt,
		UpdatedAt:   p.UpdatedAt,
	}
	return plugin
}

创建操作特点

  1. 事务创建:CreateWithTX方法支持事务操作,确保数据一致性
  2. ID生成:通过ID生成器自动生成唯一的插件标识
  3. 条件构建:使用GORM Gen生成的类型安全查询条件
  4. 数据插入:执行数据插入操作,将新插件记录存储到数据库中
  5. 数据验证:创建前进行数据验证和加密处理
  6. 重试机制:ID生成支持重试机制,避免ID冲突

创建插件的完整数据访问流程

在创建插件的场景中,数据访问层的操作流程如下:

  1. ID生成:调用genPluginID方法生成唯一的插件标识
  2. 数据验证:验证插件数据的完整性和有效性
  3. 事务开始:开启数据库事务,确保创建操作的原子性
  4. 数据插入:将插件数据插入到plugin_draft表中
  5. 关联创建:如有需要,创建相关的工具数据
  6. 事务提交:所有创建操作成功后提交事务
  7. 错误回滚:任何创建操作失败时回滚事务,保证数据一致性

这种设计确保了创建操作的安全性、可靠性和数据完整性。

数据访问层删除操作总结

创建插件在数据访问层的实现具有以下特点:

  1. 事务保证:所有创建操作在事务中执行,确保数据一致性
  2. ID生成:自动生成唯一的插件ID,避免ID冲突
  3. 类型安全:使用GORM Gen生成的类型安全查询条件,避免SQL注入风险
  4. 错误处理:完善的错误处理和事务回滚机制
  5. 数据验证:创建前进行数据验证,确保插件数据的完整性
  6. 性能优化:通过批量创建和索引优化,确保创建操作的高效执行

5.3 插件数据模型

PluginDraft数据模型

文件位置:backend/domain/plugin/internal/dal/model/plugin_draft.gen.go

该文件由GORM代码生成工具自动生成,定义了与数据库表对应的Go结构体。在创建插件操作中,该模型定义了数据库记录的结构,为创建操作提供了数据映射基础。

// Code generated by gorm.io/gen. DO NOT EDIT.
package model

const TableNamePluginDraft = "plugin_draft"

// PluginDraft plugin_draft
type PluginDraft struct {
	ID          int64                `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"`                                          // id
	SpaceID     int64                `gorm:"column:space_id;not null;comment:space id" json:"space_id"`                                             // space id
	DeveloperID int64                `gorm:"column:developer_id;not null;comment:developer id" json:"developer_id"`                                 // developer id
	PluginType  int32                `gorm:"column:plugin_type;not null;comment:plugin type" json:"plugin_type"`                                   // plugin type
	IconURI     string               `gorm:"column:icon_uri;comment:icon uri" json:"icon_uri"`                                                      // icon uri
	ServerURL   string               `gorm:"column:server_url;comment:server url" json:"server_url"`                                               // server url
	AppID       int64                `gorm:"column:app_id;comment:app id" json:"app_id"`                                                            // app id
	Manifest    *PluginManifest      `gorm:"column:manifest;comment:plugin manifest" json:"manifest"`                                              // plugin manifest
	OpenapiDoc  *Openapi3T           `gorm:"column:openapi_doc;comment:openapi document" json:"openapi_doc"`                                      // openapi document
	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
}

// TableName PluginDraft's table name
func (*PluginDraft) TableName() string {
	return TableNamePluginDraft
}
ToolDraft数据模型

文件位置:backend/domain/plugin/internal/dal/model/tool_draft.gen.go

// ToolDraft tool_draft
type ToolDraft struct {
	ID              int64      `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"`                                          // id
	PluginID        int64      `gorm:"column:plugin_id;not null;comment:plugin id" json:"plugin_id"`                                           // plugin id
	SubURL          string     `gorm:"column:sub_url;comment:sub url" json:"sub_url"`                                                          // sub url
	Method          string     `gorm:"column:method;comment:http method" json:"method"`                                                        // http method
	ActivatedStatus int32      `gorm:"column:activated_status;comment:activated status" json:"activated_status"`                               // activated status
	DebugStatus     int32      `gorm:"column:debug_status;comment:debug status" json:"debug_status"`                                           // debug status
	Operation       *Operation `gorm:"column:operation;comment:operation info" json:"operation"`                                               // operation info
	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
}

创建操作中的模型特点

  1. 主键生成:ID字段作为主键,通过ID生成器自动生成唯一标识
  2. 开发者标识:DeveloperID字段标识插件的创建者,用于权限管理
  3. 关联关系:PluginID字段建立插件与工具的关联关系,支持工具的创建
  4. 字段映射:通过gorm标签定义字段与数据库列的映射关系,为创建操作提供准确的数据存储
  5. 约束定义:包含主键、非空、注释等数据库约束,确保创建数据的完整性
  6. 复杂类型:Manifest和OpenapiDoc字段存储JSON格式的插件配置和API文档
  7. 审计追踪:CreatedAt和UpdatedAt字段记录创建和更新时间,便于追踪
  8. JSON序列化:通过json标签支持JSON序列化,便于创建操作的API响应和日志记录

5.4 GORM生成的查询接口

插件查询接口

文件位置:backend/domain/plugin/internal/dal/query/plugin_draft.gen.go

GORM Gen工具生成的类型安全查询接口,为创建插件操作提供了强大的数据访问能力。这些接口确保了创建操作的类型安全性和执行效率。

// pluginDraft plugin_draft
type pluginDraft struct {
	pluginDraftDo

	ALL         field.Asterisk
	ID          field.Int64  // id
	SpaceID     field.Int64  // space id
	DeveloperID field.Int64  // developer id
	PluginType  field.Int32  // plugin type
	IconURI     field.String // icon uri
	ServerURL   field.String // server url
	AppID       field.Int64  // app id
	Manifest    field.Field  // plugin manifest
	OpenapiDoc  field.Field  // openapi document
	CreatedAt   field.Int64  // Create Time in Milliseconds
	UpdatedAt   field.Int64  // Update Time in Milliseconds

	fieldMap map[string]field.Expr
}
工具查询接口

文件位置:backend/domain/plugin/internal/dal/query/tool_draft.gen.go

// 文件位置:backend/domain/plugin/internal/dal/query/plugin.gen.go
// plugin Latest Plugin
type plugin struct {
	pluginDo

	ALL         field.Asterisk
	ID          field.Int64  // id
	SpaceID     field.Int64  // space_id
	DeveloperID field.Int64  // developer_id
	AppID       field.Int64  // app_id
	IconURI     field.String // icon_uri
	ServerURL   field.String // server_url
	Manifest    field.Field  // manifest
	OpenapiDoc  field.Field  // openapi_doc
	PluginType  field.Int32  // plugin_type
	CreatedAt   field.Int64  // created_at
	UpdatedAt   field.Int64  // updated_at

	fieldMap map[string]field.Expr
}

// 文件位置:backend/domain/plugin/internal/dal/query/tool.gen.go
// tool Latest Tool
type tool struct {
	toolDo

	ALL             field.Asterisk
	ID              field.Int64  // id
	PluginID        field.Int64  // plugin_id
	SubURL          field.String // sub_url
	Method          field.String // method
	ActivatedStatus field.Int32  // activated_status
	DebugStatus     field.Int32  // debug_status
	Operation       field.Field  // operation
	CreatedAt       field.Int64  // created_at
	UpdatedAt       field.Int64  // updated_at

	fieldMap map[string]field.Expr
}
查询接口定义
// 文件位置:backend/domain/plugin/internal/dal/query/plugin.gen.go
type IPluginDo interface {
	gen.SubQuery
	Debug() IPluginDo
	WithContext(ctx context.Context) IPluginDo
	WithResult(fc func(tx gen.Dao)) gen.ResultInfo
	ReplaceDB(db *gorm.DB)
	ReadDB() IPluginDo
	WriteDB() IPluginDo
	// 创建操作相关方法
	Create(values ...*model.Plugin) error
	CreateInBatches(values []*model.Plugin, batchSize int) error
	Save(values ...*model.Plugin) error
	// 查询操作相关方法
	Where(conds ...gen.Condition) IPluginDo
	First() (*model.Plugin, error)
	Find() ([]*model.Plugin, error)
	FindInBatches(result *[]*model.Plugin, batchSize int, fc func(tx gen.Dao, batch int) error) error
	// 更新操作相关方法
	Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
	Updates(value interface{}) (info gen.ResultInfo, err error)
	// 删除操作相关方法
	Delete(...*model.Plugin) (info gen.ResultInfo, err error)
}

// 文件位置:backend/domain/plugin/internal/dal/query/tool.gen.go
type IToolDo interface {
	gen.SubQuery
	Debug() IToolDo
	WithContext(ctx context.Context) IToolDo
	// 创建操作相关方法
	Create(values ...*model.Tool) error
	CreateInBatches(values []*model.Tool, batchSize int) error
	Save(values ...*model.Tool) error
	// 查询操作相关方法
	Where(conds ...gen.Condition) IToolDo
	First() (*model.Tool, error)
	Find() ([]*model.Tool, error)
	// 删除操作相关方法
	Delete(...*model.Tool) (info gen.ResultInfo, err error)
}

创建操作相关接口特点

  1. Create方法:提供类型安全的创建操作,支持单条和批量数据插入
  2. Where条件:支持复杂的查询条件构建,确保数据查询的准确性
  3. 上下文支持:WithContext方法支持请求上下文传递
  4. 事务支持:支持在事务中执行创建操作,确保数据一致性
  5. 调试支持:Debug方法便于创建操作的SQL调试和优化
  6. 关联创建:Tool通过PluginID字段支持工具的关联创建
  7. 批量操作:CreateInBatches方法支持高效的批量插入操作
  8. 保存操作:Save方法支持插入或更新操作,根据主键自动判断
5.5 统一查询入口

文件位置:backend\domain\plugin\internal\dal\query\gen.go

该文件为创建插件操作提供了统一的查询入口和事务支持,确保创建操作的一致性和可靠性。

核心代码:

// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.

package query

import (
	"context"
	"database/sql"

	"gorm.io/gorm"

	"gorm.io/gen"

	"gorm.io/plugin/dbresolver"
)

var (
	Q      = new(Query)
	Plugin *plugin
	Tool   *tool
)

func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
	*Q = *Use(db, opts...)
	Plugin = &Q.Plugin
	Tool = &Q.Tool
}

func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
	return &Query{
		db:     db,
		Plugin: newPlugin(db, opts...),
		Tool:   newTool(db, opts...),
	}
}

type Query struct {
	db *gorm.DB

	Plugin plugin
	Tool   tool
}

func (q *Query) Available() bool { return q.db != nil }

func (q *Query) clone(db *gorm.DB) *Query {
	return &Query{
		db:     db,
		Plugin: q.Plugin.clone(db),
		Tool:   q.Tool.clone(db),
	}
}

func (q *Query) ReadDB() *Query {
	return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
}

func (q *Query) WriteDB() *Query {
	return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
}

func (q *Query) ReplaceDB(db *gorm.DB) *Query {
	return &Query{
		db:     db,
		Plugin: q.Plugin.replaceDB(db),
		Tool:   q.Tool.replaceDB(db),
	}
}

type queryCtx struct {
	Plugin IPluginDo
	Tool   IToolDo
}

func (q *Query) WithContext(ctx context.Context) *queryCtx {
	return &queryCtx{
		Plugin: q.Plugin.WithContext(ctx),
		Tool:   q.Tool.WithContext(ctx),
	}
}

func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
	return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
}

func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
	tx := q.db.Begin(opts...)
	return &QueryTx{Query: q.clone(tx), Error: tx.Error}
}

type QueryTx struct {
	*Query
	Error error
}

func (q *QueryTx) Commit() error {
	return q.db.Commit().Error
}

func (q *QueryTx) Rollback() error {
	return q.db.Rollback().Error
}

func (q *QueryTx) SavePoint(name string) error {
	return q.db.SavePoint(name).Error
}

func (q *QueryTx) RollbackTo(name string) error {
	return q.db.RollbackTo(name).Error
}

创建操作查询入口特点

  1. 全局查询对象:提供全局的Plugin和Tool查询对象,便于创建操作的统一管理
  2. 事务支持:Transaction方法支持在事务中执行创建操作,确保数据一致性
  3. 读写分离:ReadDB和WriteDB方法支持数据库读写分离,创建操作使用WriteDB
  4. 上下文传递:WithContext方法支持请求上下文在创建操作中的传递
  5. 数据库切换:ReplaceDB方法支持动态切换数据库连接,便于多环境部署
  6. 事务管理:Begin、Commit、Rollback等方法提供完整的事务管理能力
  7. 多表支持:同时支持Plugin和Tool两个表的创建操作
  8. 连接池管理:支持数据库连接池的动态管理和优化

5.6 数据访问层创建操作架构总结

创建插件在数据访问层的实现体现了现代Go应用的最佳实践:

技术特点

  1. 类型安全:使用GORM Gen生成类型安全的查询接口,避免SQL注入和类型错误
  2. 分层设计:Repository接口抽象数据访问,DAO实现具体的数据库操作
  3. 错误处理:统一的错误码包装机制,便于上层进行错误分类和处理
  4. 事务支持:完整的事务支持,确保创建操作的原子性
  5. 性能优化:合理的索引设计和批量插入,确保创建操作的高效执行
  6. ID生成:支持插件及其关联工具的唯一ID生成操作

安全保障

  1. 权限验证:通过DeveloperID字段确保插件创建者的身份标识
  2. 数据验证:创建前验证插件数据的完整性和有效性
  3. 唯一性保证:通过ID生成器确保插件ID的唯一性
  4. 审计追踪:完整的时间戳记录,支持创建操作的审计和追踪
  5. 关联创建:确保创建插件时正确建立相关的工具数据关联

创建操作流程

  1. 接口调用:上层通过Repository接口调用CreateDraftPlugin方法
  2. ID生成:生成唯一的插件ID,避免ID冲突
  3. 事务开启:开启数据库事务确保操作的原子性
  4. 数据插入:将插件数据插入到plugin_draft表中
  5. 事务提交:所有创建操作成功后提交事务
  6. 错误处理:任何步骤失败都会回滚事务并返回错误

这种设计确保了创建插件操作的安全性、可靠性和高性能,为上层业务逻辑提供了坚实的数据访问基础。

数据模型与查询文件依赖关系
数据库表结构 (schema.sql)(plugin_draft、tool_draft表)
    ↓    gen_orm_query.go
模型文件 (model/plugin_draft.gen.go, model/tool_draft.gen.go) - 生成模型
    ↓
查询文件 (query/plugin_draft.gen.go, query/tool_draft.gen.go) - 依赖对应模型
    ↓
统一入口 (query/gen.go) - 依赖所有查询文件

数据访问层架构总结

分层架构

业务服务层 (Service)
       ↓
仓储接口层 (Repository Interface)
       ↓
数据访问层 (DAO Implementation)
       ↓
GORM查询层 (Generated Query)
       ↓
数据模型层 (Generated Model)
       ↓
数据库层 (MySQL)

创建插件在数据访问层的完整流程

  1. 接口定义PluginRepository.CreateDraftPlugin(ctx, plugin) 定义创建操作契约
  2. DAO实现PluginDraftDAO.Create(ctx, plugin)PluginDraftDAO.CreateWithTX(tx, plugin) 实现具体创建逻辑
  3. ID生成:使用ID生成器为插件生成唯一标识
  4. 事务管理:使用事务确保插件数据的一致性创建
  5. 数据插入:将插件数据插入到plugin_draft表中
  6. 错误处理:包装创建异常为统一错误码
  7. 结果返回:创建成功返回插件ID,失败返回包装后的错误

设计优势

  1. 接口抽象:通过Repository接口实现数据访问的抽象化
  2. 代码生成:使用GORM Gen自动生成类型安全的查询代码
  3. 错误处理:统一的错误包装和处理机制
  4. 事务支持:通过context传递支持数据库事务
  5. 创建安全:通过ID生成器确保唯一性,避免ID冲突
  6. 性能优化:合理的索引设计和批量插入优化
  7. 可测试性:清晰的分层结构便于单元测试
  8. 可维护性:代码生成减少手工编写,降低维护成本
  9. 关联创建:支持插件及其关联数据的一致性创建

创建操作的技术特点

  • 数据插入:当前实现为数据插入,将新插件记录存储到数据库中
  • 事务操作:使用数据库事务确保插件数据的一致性创建
  • 索引优化:基于主键ID的插入操作,具有最佳的写入性能
  • 错误分类:通过错误码区分不同类型的创建异常
  • 审计支持:可通过数据库日志追踪创建操作的执行情况
  • 关联建立:确保创建插件时正确建立相关的数据关联
Logo

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

更多推荐