Coze源码分析-资源库-编辑工作流-后端源码-数据存储/安全/错误

7. 数据存储层

7.1 MySQL数据库表结构

工作流编辑相关的MySQL表结构设计,包含了版本控制、锁定状态等编辑相关字段。

sql 复制代码
-- 工作流草稿表
CREATE TABLE `workflow_draft` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `workflow_id` varchar(64) NOT NULL COMMENT '工作流唯一标识',
  `space_id` bigint(20) NOT NULL COMMENT '所属空间ID',
  `name` varchar(100) NOT NULL COMMENT '工作流名称',
  `description` text COMMENT '工作流描述',
  `creator_id` bigint(20) NOT NULL COMMENT '创建者ID',
  `editor_id` bigint(20) NOT NULL COMMENT '最后编辑者ID',
  `version` varchar(64) NOT NULL COMMENT '版本号(乐观锁)',
  `edit_count` bigint(20) NOT NULL DEFAULT '0' COMMENT '编辑次数',
  `lock_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '锁定状态:0-未锁定,1-已锁定',
  `lock_owner_id` bigint(20) DEFAULT NULL COMMENT '锁定所有者ID',
  `lock_expire_at` bigint(20) DEFAULT NULL COMMENT '锁定过期时间戳',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-草稿,1-已发布',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_workflow_id` (`workflow_id`),
  KEY `idx_space_id` (`space_id`),
  KEY `idx_creator_id` (`creator_id`),
  KEY `idx_editor_id` (`editor_id`),
  KEY `idx_version` (`version`),
  KEY `idx_lock_status` (`lock_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工作流草稿表';

-- 画布信息表
CREATE TABLE `canvas_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `workflow_id` varchar(64) NOT NULL COMMENT '工作流唯一标识',
  `nodes` json DEFAULT NULL COMMENT '节点数据',
  `edges` json DEFAULT NULL COMMENT '边数据',
  `config` json DEFAULT NULL COMMENT '画布配置',
  `version` bigint(20) NOT NULL DEFAULT '1' COMMENT '版本号',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_workflow_id` (`workflow_id`),
  KEY `idx_version` (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='画布信息表';

7.2 ElasticSearch索引架构

工作流编辑相关的ElasticSearch索引设计,支持实时搜索和过滤。

go 复制代码
// 工作流ES索引映射
func (s *WorkflowSearchService) CreateWorkflowIndex(ctx context.Context) error {
    mappings := map[string]interface{}{
        "mappings": map[string]interface{}{
            "properties": map[string]interface{}{
                "res_id": map[string]interface{}{
                    "type": "long",
                },
                "res_type": map[string]interface{}{
                    "type": "integer",
                },
                "workflow_id": map[string]interface{}{
                    "type": "keyword",
                },
                "space_id": map[string]interface{}{
                    "type": "long",
                },
                "name": map[string]interface{}{
                    "type": "text",
                    "analyzer": "ik_max_word",
                },
                "description": map[string]interface{}{
                    "type": "text",
                    "analyzer": "ik_max_word",
                },
                "creator_id": map[string]interface{}{
                    "type": "long",
                },
                "editor_id": map[string]interface{}{
                    "type": "long",
                },
                "version": map[string]interface{}{
                    "type": "keyword",
                },
                "edit_count": map[string]interface{}{
                    "type": "long",
                },
                "create_time": map[string]interface{}{
                    "type": "long",
                },
                "update_time": map[string]interface{}{
                    "type": "long",
                },
                "status": map[string]interface{}{
                    "type": "integer",
                },
            },
        },
    }
    
    return s.esClient.CreateIndex(ctx, "coze_resource", mappings)
}

7.3 数据同步机制

工作流编辑后的数据同步机制,确保数据库和搜索索引的一致性。

go 复制代码
// 工作流编辑事件处理
func (h *resourceHandlerImpl) HandleWorkflowEditEvent(ctx context.Context, event *WorkflowEditEvent) error {
    // 1. 准备索引更新数据
    doc := map[string]interface{}{
        "res_id":      event.WorkflowID,
        "res_type":    2, // 工作流类型
        "workflow_id": event.WorkflowID,
        "space_id":    event.SpaceID,
        "name":        event.Name,
        "description": event.Description,
        "creator_id":  event.CreatorID,
        "editor_id":   event.EditorID,
        "version":     event.Version,
        "edit_count":  event.EditCount,
        "update_time": event.UpdatedAt.Unix(),
        "status":      event.Status,
    }
    
    // 2. 执行ES索引更新
    err := h.esClient.Update(ctx, "coze_resource", fmt.Sprintf("%d", event.WorkflowID), doc)
    if err != nil {
        // 记录失败并尝试重试
        return h.recordFailedSync(ctx, event, err)
    }
    
    // 3. 更新缓存
    cacheKey := fmt.Sprintf("workflow:%d", event.WorkflowID)
    h.cacheClient.Delete(ctx, cacheKey)
    
    // 4. 同步锁定状态
    if err := h.syncWorkflowLockStatus(ctx, event.WorkflowID); err != nil {
        logs.CtxWarnf(ctx, "同步锁定状态失败: %v", err)
    }
    
    return nil
}

// 同步工作流锁定状态
func (h *resourceHandlerImpl) syncWorkflowLockStatus(ctx context.Context, workflowID int64) error {
    // 实现锁定状态同步逻辑
    // ...
    return nil
}

// 记录同步失败
func (h *resourceHandlerImpl) recordFailedSync(ctx context.Context, event *WorkflowEditEvent, err error) error {
    // 记录失败并安排重试
    logs.CtxErrorf(ctx, "同步工作流失败: %v, event: %+v", err, event)
    // 实现重试逻辑
    // ...
    return err
}

8. 工作流编辑安全和权限验证机制

8.1 JWT身份认证

JWT Token验证中间件,确保用户身份的合法性。

go 复制代码
// JWT验证中间件
func JWTAuthMiddleware() app.HandlerFunc {
    return func(c *app.RequestContext) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.AbortWithStatusJSON(consts.StatusUnauthorized, common.ErrorResponse{
                Code:    int64(consts.StatusUnauthorized),
                Message: "未提供认证令牌",
            })
            return
        }
        
        // 移除Bearer前缀
        if strings.HasPrefix(token, "Bearer ") {
            token = token[7:]
        }
        
        // 解析Token
        claims, err := ParseToken(token)
        if err != nil {
            c.AbortWithStatusJSON(consts.StatusUnauthorized, common.ErrorResponse{
                Code:    int64(consts.StatusUnauthorized),
                Message: "无效的认证令牌",
            })
            return
        }
        
        // 将用户信息存入上下文
        c.Set("user_id", claims.UserID)
        c.Set("user_name", claims.UserName)
        
        c.Next()
    }
}

// ParseToken 解析JWT Token
func ParseToken(tokenString string) (*Claims, error) {
    // 实现Token解析逻辑
    // ...
    return claims, nil
}

8.2 工作空间权限控制

工作空间级别的权限控制,确保用户只能访问有权限的工作空间。

go 复制代码
// 工作空间访问权限验证
func (s *SpaceDomainService) HasSpaceAccess(ctx context.Context, userID, spaceID int64) (bool, error) {
    // 检查用户是否为空间成员
    var count int64
    err := s.db.Model(&SpaceMember{}).
        Where("space_id = ? AND user_id = ?", spaceID, userID).
        Count(&count).Error
    if err != nil {
        return false, fmt.Errorf("检查空间访问权限失败: %w", err)
    }
    
    return count > 0, nil
}

// 检查是否为空间管理员
func (s *SpaceDomainService) IsSpaceAdmin(ctx context.Context, userID, spaceID int64) (bool, error) {
    var count int64
    err := s.db.Model(&SpaceMember{}).
        Where("space_id = ? AND user_id = ? AND role = ?", spaceID, userID, model.RoleAdmin).
        Count(&count).Error
    if err != nil {
        return false, fmt.Errorf("检查空间管理员权限失败: %w", err)
    }
    
    return count > 0, nil
}

8.3 工作流编辑权限验证

工作流级别的编辑权限验证,确保用户只能编辑有权限的工作流。

go 复制代码
// 资源级权限验证
func (s *WorkflowDomainService) validateEditPermission(ctx context.Context, userID int64, workflow *Workflow) error {
    // 1. 检查是否为工作流所有者
    if workflow.CreatorID == userID {
        return nil
    }
    
    // 2. 检查是否为空间管理员
    isAdmin, err := s.spaceService.IsSpaceAdmin(ctx, userID, workflow.SpaceID)
    if err != nil {
        return fmt.Errorf("检查空间管理员权限失败: %w", err)
    }
    if isAdmin {
        return nil
    }
    
    // 3. 检查是否为工作流编辑者
    isEditor, err := s.workflowRepo.IsEditor(ctx, workflow.ID, userID)
    if err != nil {
        return fmt.Errorf("检查编辑者权限失败: %w", err)
    }
    if isEditor {
        return nil
    }
    
    return errorx.New(errno.ErrWorkflowPermissionCode, 
        errorx.KV("msg", "用户无权限编辑此工作流"),
        errorx.KV("user_id", userID),
        errorx.KV("workflow_id", workflow.ID),
        errorx.KV("required_role", model.RoleDeveloper))
}

8.4 工作流编辑API访问控制

工作流编辑API的访问控制,包括频率限制、参数验证等安全措施。

go 复制代码
// 编辑请求频率限制
func RateLimitMiddleware() app.HandlerFunc {
    return func(c *app.RequestContext) {
        userID := c.GetInt64("user_id")
        key := fmt.Sprintf("rate_limit:workflow_edit:%d", userID)
        
        // 使用Redis实现频率限制
        current, err := redisClient.Incr(ctx, key).Result()
        if err != nil {
            c.Next()
            return
        }
        
        if current == 1 {
            // 设置过期时间
            redisClient.Expire(ctx, key, time.Minute)
        }
        
        // 限制每分钟最多10次编辑请求
        if current > 10 {
            c.AbortWithStatusJSON(consts.StatusTooManyRequests, common.ErrorResponse{
                Code:    int64(consts.StatusTooManyRequests),
                Message: "编辑请求过于频繁,请稍后再试",
            })
            return
        }
        
        c.Next()
    }
}

// 编辑操作安全验证
func (s *WorkflowEditValidator) ValidateWorkflowEdit(ctx context.Context, req *UpdateWorkflowRequest, userID int64) error {
    // 1. 身份验证
    if userID == 0 {
        return errors.New("用户未登录,无法编辑工作流")
    }
    
    // 2. 权限检查
    if !s.permissionChecker.CanEditWorkflow(ctx, userID, req.WorkflowID) {
        return errors.New("用户没有编辑该工作流的权限")
    }
    
    // 3. 锁定验证
    if err := s.lockValidator.ValidateLock(ctx, req.WorkflowID, req.LockID, userID); err != nil {
        return fmt.Errorf("锁定验证失败: %w", err)
    }
    
    // 4. 版本验证
    if err := s.versionValidator.ValidateVersion(ctx, req.WorkflowID, req.Version); err != nil {
        return fmt.Errorf("版本验证失败: %w", err)
    }
    
    // 5. 参数验证
    if err := s.paramValidator.ValidateEditParams(req); err != nil {
        return fmt.Errorf("参数验证失败: %w", err)
    }
    
    // 6. 画布数据安全性检查
    if req.CanvasData != "" {
        if err := s.validateCanvasDataSecurity(req.CanvasData); err != nil {
            return fmt.Errorf("画布数据安全检查失败: %w", err)
        }
    }
    
    return nil
}

9. 工作流编辑错误处理和日志记录

9.1 工作流编辑错误类型定义

定义了工作流编辑过程中可能遇到的各种错误类型和错误码。

go 复制代码
// 工作流编辑错误码定义
const (
    ErrWorkflowNotExistCode     = 10001 // 工作流不存在
    ErrWorkflowPermissionCode   = 10002 // 无权限编辑工作流
    ErrWorkflowLockMismatch     = 10003 // 锁定信息不匹配
    ErrWorkflowLockNotExist     = 10004 // 锁定不存在
    ErrWorkflowLockExpired      = 10005 // 锁定已过期
    ErrWorkflowVersionConflict  = 10006 // 版本冲突
    ErrWorkflowInvalidParamCode = 10007 // 参数无效
    ErrWorkflowInvalidCanvas    = 10008 // 画布数据无效
    ErrWorkflowSystemError      = 10009 // 系统错误
)

// 工作流编辑错误类型
const (
    ErrorTypeBusiness  = "business"  // 业务错误
    ErrorTypeSystem    = "system"    // 系统错误
    ErrorTypeNetwork   = "network"   // 网络错误
)

// WorkflowEditError 工作流编辑错误
type WorkflowEditError struct {
    Code       int64            `json:"code"`       // 错误码
    Message    string           `json:"message"`    // 错误消息
    ErrorType  string           `json:"error_type"` // 错误类型
    Details    map[string]interface{} `json:"details"`    // 错误详情
    Timestamp  int64            `json:"timestamp"`  // 错误时间戳
    RequestID  string           `json:"request_id"` // 请求ID
}

// NewWorkflowEditError 创建工作流编辑错误
func NewWorkflowEditError(code int64, message string, errorType string, details map[string]interface{}) *WorkflowEditError {
    return &WorkflowEditError{
        Code:      code,
        Message:   message,
        ErrorType: errorType,
        Details:   details,
        Timestamp: time.Now().Unix(),
        RequestID: generateRequestID(),
    }
}

9.2 工作流编辑错误处理

工作流编辑过程中的错误处理机制,包括错误捕获、包装、记录和响应。

go 复制代码
// 统一错误响应格式
type WorkflowEditErrorResponse struct {
    Code       int64       `json:"code"`       // 错误码
    Message    string      `json:"message"`    // 错误消息
    ErrorType  string      `json:"error_type"` // 错误类型
    LockInfo   *LockInfo   `json:"lock_info,omitempty"` // 锁定信息
    RequestID  string      `json:"request_id"` // 请求ID
    Timestamp  int64       `json:"timestamp"`  // 时间戳
}

type LockInfo struct {
    LockOwnerID int64 `json:"lock_owner_id"` // 锁定所有者ID
    ExpireTime  int64 `json:"expire_time"`  // 锁定过期时间
}

// 错误处理中间件
func WorkflowEditErrorHandlerMiddleware() app.HandlerFunc {
    return func(c *app.RequestContext) {
        c.Next()
        
        // 处理工作流编辑相关错误
        if len(c.Errors) > 0 {
            for _, err := range c.Errors {
                if workflowErr, ok := err.Err.(*WorkflowEditError); ok {
                    handleWorkflowEditError(c, workflowErr)
                    return
                }
            }
        }
    }
}

// 处理工作流编辑业务错误
func handleWorkflowEditBusinessError(c *app.RequestContext, err *WorkflowEditError) {
    resp := WorkflowEditErrorResponse{
        Code:      err.Code,
        Message:   err.Message,
        ErrorType: err.ErrorType,
        RequestID: err.RequestID,
        Timestamp: err.Timestamp,
    }
    
    // 如果是锁定相关错误,包含锁定信息
    if err.Code == ErrWorkflowLockMismatch || err.Code == ErrWorkflowLockExpired {
        if lockOwnerID, ok := err.Details["lock_owner_id"].(int64); ok {
            if expireTime, ok := err.Details["expire_time"].(int64); ok {
                resp.LockInfo = &LockInfo{
                    LockOwnerID: lockOwnerID,
                    ExpireTime:  expireTime,
                }
            }
        }
    }
    
    c.JSON(consts.StatusBadRequest, resp)
}

// 处理工作流编辑锁定错误
func handleWorkflowEditLockError(c *app.RequestContext, err *WorkflowEditError) {
    resp := WorkflowEditErrorResponse{
        Code:      err.Code,
        Message:   err.Message,
        ErrorType: err.ErrorType,
        RequestID: err.RequestID,
        Timestamp: err.Timestamp,
    }
    
    // 提取锁定信息
    if lockInfo, ok := err.Details["lock_info"].(*LockInfo); ok {
        resp.LockInfo = lockInfo
    }
    
    c.JSON(consts.StatusConflict, resp)
}

// 处理工作流编辑系统错误
func handleWorkflowEditSystemError(c *app.RequestContext, err *WorkflowEditError) {
    resp := WorkflowEditErrorResponse{
        Code:      err.Code,
        Message:   "系统内部错误,请稍后重试",
        ErrorType: err.ErrorType,
        RequestID: err.RequestID,
        Timestamp: err.Timestamp,
    }
    
    // 记录详细错误日志
    logs.CtxErrorf(c.Request.Context(), "Workflow edit system error: %v, details: %+v", err.Message, err.Details)
    
    c.JSON(consts.StatusInternalServerError, resp)
}

9.3 工作流编辑日志记录策略

工作流编辑过程中的日志记录策略,包括日志级别、格式和内容规范。

go 复制代码
// 日志级别定义
const (
    LogLevelDebug = "debug"
    LogLevelInfo  = "info"
    LogLevelWarn  = "warn"
    LogLevelError = "error"
    LogLevelFatal = "fatal"
)

// 结构化日志格式
func logWorkflowEditOperation(ctx context.Context, level string, msg string, fields ...interface{}) {
    switch level {
    case LogLevelDebug:
        logs.CtxDebugf(ctx, msg, fields...)
    case LogLevelInfo:
        logs.CtxInfof(ctx, msg, fields...)
    case LogLevelWarn:
        logs.CtxWarnf(ctx, msg, fields...)
    case LogLevelError:
        logs.CtxErrorf(ctx, msg, fields...)
    case LogLevelFatal:
        logs.CtxFatalf(ctx, msg, fields...)
    }
}

// 工作流编辑审计日志
func logWorkflowEditAudit(ctx context.Context, userID int64, workflowID int64, action string, success bool, errorMsg string) {
    logFields := []interface{}{
        "user_id", userID,
        "workflow_id", workflowID,
        "action", action,
        "success", success,
        "timestamp", time.Now().Unix(),
        "request_id", getRequestID(ctx),
    }
    
    if !success {
        logFields = append(logFields, "error_msg", errorMsg)
    }
    
    logs.CtxInfof(ctx, "Workflow edit audit", logFields...)
}

// 记录工作流编辑详细日志
func (s *WorkflowApplicationService) UpdateWorkflowMeta(ctx context.Context, req *UpdateWorkflowMetaRequest) (*UpdateWorkflowMetaResponse, error) {
    // 记录开始编辑日志
    logs.CtxInfof(ctx, "Start updating workflow meta, userID=%d, workflowID=%d, version=%s", 
        req.UserID, req.WorkflowID, req.Version)
    
    // 记录请求参数(敏感信息脱敏)
    safeReq := *req
    if len(safeReq.CanvasData) > 100 {
        safeReq.CanvasData = safeReq.CanvasData[:100] + "...(truncated)"
    }
    logs.CtxDebugf(ctx, "Update workflow meta request: %+v", safeReq)
    
    defer func() {
        // 记录编辑结束日志
        logs.CtxInfof(ctx, "Finish updating workflow meta, userID=%d, workflowID=%d", 
            req.UserID, req.WorkflowID)
    }()
    
    // 执行编辑逻辑
    // ...
    
    return resp, nil
}
相关推荐
用户9623779544815 小时前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机18 小时前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机18 小时前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户9623779544819 小时前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star19 小时前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954481 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
吾鳴2 天前
扣子(Coze)实战:小白也能变大厨?Nano Banana 2一键制作精美菜谱
coze
cipher3 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行6 天前
网络安全总结
安全·web安全
red1giant_star6 天前
手把手教你用Vulhub复现ecshop collection_list-sqli漏洞(附完整POC)
安全