feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package service
import (
"context"
"time"
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
resourceCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossplugin"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossworkflow"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
"github.com/coze-dev/coze-studio/backend/domain/app/repository"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
commonConsts "github.com/coze-dev/coze-studio/backend/types/consts"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (a *appServiceImpl) PublishAPP(ctx context.Context, req *PublishAPPRequest) (resp *PublishAPPResponse, err error) {
err = a.checkCanPublishAPP(ctx, req)
if err != nil {
return nil, err
}
recordID, err := a.createPublishVersion(ctx, req)
if err != nil {
return nil, err
}
success, err := a.publishByConnectors(ctx, recordID, req)
if err != nil {
logs.CtxErrorf(ctx, "publish by connectors failed, recordID=%d, err=%v", recordID, err)
}
resp = &PublishAPPResponse{
Success: success,
PublishRecordID: recordID,
}
return resp, nil
}
func (a *appServiceImpl) publishByConnectors(ctx context.Context, recordID int64, req *PublishAPPRequest) (success bool, err error) {
defer func() {
if err != nil {
updateErr := a.APPRepo.UpdateAPPPublishStatus(ctx, &repository.UpdateAPPPublishStatusRequest{
RecordID: recordID,
PublishStatus: entity.PublishStatusOfPackFailed,
})
if updateErr != nil {
logs.CtxErrorf(ctx, "UpdateAPPPublishStatus failed, recordID=%d, err=%v", recordID, updateErr)
}
}
}()
connectorIDs := make([]int64, 0, len(req.ConnectorPublishConfigs))
for cid := range req.ConnectorPublishConfigs {
connectorIDs = append(connectorIDs, cid)
}
failedResources, err := a.packResources(ctx, req.APPID, req.Version, connectorIDs)
if err != nil {
return false, err
}
if len(failedResources) > 0 {
logs.CtxWarnf(ctx, "packResources failed, recordID=%d, len=%d", recordID, len(failedResources))
processErr := a.packResourcesFailedPostProcess(ctx, recordID, failedResources)
if processErr != nil {
logs.CtxErrorf(ctx, "packResourcesFailedPostProcess failed, recordID=%d, err=%v", recordID, processErr)
}
return false, nil
}
for cid := range req.ConnectorPublishConfigs {
switch cid {
case commonConsts.APIConnectorID:
updateSuccessErr := a.APPRepo.UpdateConnectorPublishStatus(ctx, recordID, entity.ConnectorPublishStatusOfSuccess)
if updateSuccessErr == nil {
continue
}
logs.CtxErrorf(ctx, "failed to update connector '%d' publish status to success, err=%v", cid, updateSuccessErr)
updateFailedErr := a.APPRepo.UpdateAPPPublishStatus(ctx, &repository.UpdateAPPPublishStatusRequest{
RecordID: recordID,
PublishStatus: entity.PublishStatusOfPackFailed,
})
if updateFailedErr != nil {
logs.CtxWarnf(ctx, "failed to update connector '%d' publish status to failed, err=%v", cid, updateFailedErr)
}
default:
continue
}
}
err = a.APPRepo.UpdateAPPPublishStatus(ctx, &repository.UpdateAPPPublishStatusRequest{
RecordID: recordID,
PublishStatus: entity.PublishStatusOfPublishDone,
})
if err != nil {
return false, errorx.Wrapf(err, "UpdateAPPPublishStatus failed, recordID=%d", recordID)
}
return true, nil
}
func (a *appServiceImpl) checkCanPublishAPP(ctx context.Context, req *PublishAPPRequest) (err error) {
exist, err := a.APPRepo.CheckAPPVersionExist(ctx, req.APPID, req.Version)
if err != nil {
return errorx.Wrapf(err, "CheckAPPVersionExist failed, appID=%d, version=%s", req.APPID, req.Version)
}
if exist {
return errorx.New(errno.ErrAppRecordNotFound)
}
return nil
}
func (a *appServiceImpl) createPublishVersion(ctx context.Context, req *PublishAPPRequest) (recordID int64, err error) {
draftAPP, exist, err := a.APPRepo.GetDraftAPP(ctx, req.APPID)
if err != nil {
return 0, errorx.Wrapf(err, "GetDraftAPP failed, appID=%d", req.APPID)
}
if !exist {
return 0, errorx.New(errno.ErrAppRecordNotFound)
}
draftAPP.PublishedAtMS = ptr.Of(time.Now().UnixMilli())
draftAPP.Version = &req.Version
draftAPP.VersionDesc = &req.VersionDesc
publishRecords := make([]*entity.ConnectorPublishRecord, 0, len(req.ConnectorPublishConfigs))
for cid, conf := range req.ConnectorPublishConfigs {
publishRecords = append(publishRecords, &entity.ConnectorPublishRecord{
ConnectorID: cid,
PublishStatus: entity.ConnectorPublishStatusOfDefault,
PublishConfig: conf,
})
draftAPP.ConnectorIDs = append(draftAPP.ConnectorIDs, cid)
}
recordID, err = a.APPRepo.CreateAPPPublishRecord(ctx, &entity.PublishRecord{
APP: draftAPP,
ConnectorPublishRecords: publishRecords,
})
if err != nil {
return 0, errorx.Wrapf(err, "CreateAPPPublishRecord failed, appID=%d", req.APPID)
}
return recordID, nil
}
func (a *appServiceImpl) packResources(ctx context.Context, appID int64, version string, connectorIDs []int64) (failedResources []*entity.PackResourceFailedInfo, err error) {
failedPlugins, allDraftPlugins, err := a.packPlugins(ctx, appID, version)
if err != nil {
return nil, err
}
workflowFailedInfoList, err := a.packWorkflows(ctx, appID, version,
slices.Transform(allDraftPlugins, func(a *plugin.PluginInfo) int64 {
return a.ID
}), connectorIDs)
if err != nil {
return nil, err
}
length := len(failedPlugins) + len(workflowFailedInfoList)
if length == 0 {
return nil, nil
}
failedResources = append(failedResources, failedPlugins...)
failedResources = append(failedResources, workflowFailedInfoList...)
return failedResources, nil
}
func (a *appServiceImpl) packPlugins(ctx context.Context, appID int64, version string) (failedInfo []*entity.PackResourceFailedInfo, allDraftPlugins []*plugin.PluginInfo, err error) {
res, err := crossplugin.DefaultSVC().PublishAPPPlugins(ctx, &plugin.PublishAPPPluginsRequest{
APPID: appID,
Version: version,
})
if err != nil {
return nil, nil, errorx.Wrapf(err, "PublishAPPPlugins failed, appID=%d, version=%s", appID, version)
}
failedInfo = make([]*entity.PackResourceFailedInfo, 0, len(res.FailedPlugins))
for _, p := range res.FailedPlugins {
failedInfo = append(failedInfo, &entity.PackResourceFailedInfo{
ResID: p.ID,
ResType: resourceCommon.ResType_Plugin,
ResName: p.GetName(),
})
}
return failedInfo, res.AllDraftPlugins, nil
}
func (a *appServiceImpl) packWorkflows(ctx context.Context, appID int64, version string, allDraftPluginIDs []int64, connectorIDs []int64) (workflowFailedInfoList []*entity.PackResourceFailedInfo, err error) {
issues, err := crossworkflow.DefaultSVC().ReleaseApplicationWorkflows(ctx, appID, &crossworkflow.ReleaseWorkflowConfig{
Version: version,
PluginIDs: allDraftPluginIDs,
ConnectorIDs: connectorIDs,
})
if err != nil {
return nil, errorx.Wrapf(err, "ReleaseApplicationWorkflows failed, appID=%d, version=%s", appID, version)
}
if len(issues) == 0 {
return workflowFailedInfoList, nil
}
workflowFailedInfoList = make([]*entity.PackResourceFailedInfo, 0, len(issues))
for _, issue := range issues {
workflowFailedInfoList = append(workflowFailedInfoList, &entity.PackResourceFailedInfo{
ResID: issue.WorkflowID,
ResType: resourceCommon.ResType_Workflow,
ResName: issue.WorkflowName,
})
}
return workflowFailedInfoList, nil
}
func (a *appServiceImpl) packResourcesFailedPostProcess(ctx context.Context, recordID int64, packFailedInfo []*entity.PackResourceFailedInfo) (err error) {
publishFailedInfo := &entity.PublishRecordExtraInfo{
PackFailedInfo: packFailedInfo,
}
err = a.APPRepo.UpdateAPPPublishStatus(ctx, &repository.UpdateAPPPublishStatusRequest{
RecordID: recordID,
PublishStatus: entity.PublishStatusOfPackFailed,
PublishRecordExtraInfo: publishFailedInfo,
})
if err != nil {
return errorx.Wrapf(err, "UpdateAPPPublishStatus failed, recordID=%d", recordID)
}
return nil
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package service
import (
"context"
connectorModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
)
type AppService interface {
CreateDraftAPP(ctx context.Context, req *CreateDraftAPPRequest) (appID int64, err error)
GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, err error)
DeleteDraftAPP(ctx context.Context, appID int64) (err error)
UpdateDraftAPP(ctx context.Context, req *UpdateDraftAPPRequest) (err error)
GetDraftAPPResources(ctx context.Context, appID int64) (resources []*entity.Resource, err error)
PublishAPP(ctx context.Context, req *PublishAPPRequest) (resp *PublishAPPResponse, err error)
GetAPPPublishRecord(ctx context.Context, req *GetAPPPublishRecordRequest) (record *entity.PublishRecord, published bool, err error)
GetAPPAllPublishRecords(ctx context.Context, appID int64) (records []*entity.PublishRecord, err error)
GetPublishConnectorList(ctx context.Context, req *GetPublishConnectorListRequest) (resp *GetPublishConnectorListResponse, err error)
}
type CreateDraftAPPRequest struct {
SpaceID int64
OwnerID int64
Name string
Desc string
IconURI string
}
type UpdateDraftAPPRequest struct {
APPID int64
Name *string
Desc *string
IconURI *string
}
type DuplicateDraftAPPRequest struct {
APPID int64
Name string
Desc string
IconURI string
}
type PublishAPPRequest struct {
APPID int64
Version string
VersionDesc string
ConnectorPublishConfigs map[int64]entity.PublishConfig
}
type PublishAPPResponse struct {
PublishRecordID int64
Success bool
}
type GetAPPPublishRecordRequest struct {
APPID int64
RecordID *int64
Oldest bool // Get the oldest record if Oldest is true and RecordID is nil; otherwise, get the latest record
}
type GetPublishConnectorListRequest struct {
}
type GetPublishConnectorListResponse struct {
Connectors []*connectorModel.Connector
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package service
import (
"context"
"sort"
"gorm.io/gorm"
connectorModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector"
databaseModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossconnector"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossdatabase"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossknowledge"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossplugin"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
"github.com/coze-dev/coze-studio/backend/domain/app/repository"
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
type Components struct {
IDGen idgen.IDGenerator
DB *gorm.DB
APPRepo repository.AppRepository
}
func NewService(components *Components) AppService {
return &appServiceImpl{
Components: components,
}
}
type appServiceImpl struct {
*Components
}
func (a *appServiceImpl) CreateDraftAPP(ctx context.Context, req *CreateDraftAPPRequest) (appID int64, err error) {
app := &entity.APP{
SpaceID: req.SpaceID,
Name: &req.Name,
Desc: &req.Desc,
IconURI: &req.IconURI,
OwnerID: req.OwnerID,
}
appID, err = a.APPRepo.CreateDraftAPP(ctx, app)
if err != nil {
return 0, errorx.Wrapf(err, "CreateDraftAPP failed, spaceID=%d", req.SpaceID)
}
return appID, nil
}
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
}
func (a *appServiceImpl) DeleteDraftAPP(ctx context.Context, appID int64) (err error) {
err = a.APPRepo.DeleteDraftAPP(ctx, appID)
if err != nil {
return errorx.Wrapf(err, "DeleteDraftAPP failed, appID=%d", appID)
}
return nil
}
func (a *appServiceImpl) UpdateDraftAPP(ctx context.Context, req *UpdateDraftAPPRequest) (err error) {
app := &entity.APP{
ID: req.APPID,
Name: req.Name,
Desc: req.Desc,
IconURI: req.IconURI,
}
err = a.APPRepo.UpdateDraftAPP(ctx, app)
if err != nil {
return errorx.Wrapf(err, "UpdateDraftAPP failed, appID=%d", req.APPID)
}
return nil
}
func (a *appServiceImpl) GetAPPPublishRecord(ctx context.Context, req *GetAPPPublishRecordRequest) (record *entity.PublishRecord, exist bool, err error) {
record, exist, err = a.APPRepo.GetPublishRecord(ctx, &repository.GetPublishRecordRequest{
APPID: req.APPID,
RecordID: req.RecordID,
OldestSuccess: req.Oldest,
})
if err != nil {
return nil, false, errorx.Wrapf(err, "GetPublishRecord failed, appID=%d", req.APPID)
}
return record, exist, nil
}
func (a *appServiceImpl) GetAPPAllPublishRecords(ctx context.Context, appID int64) (records []*entity.PublishRecord, err error) {
records, err = a.APPRepo.GetAPPAllPublishRecords(ctx, appID,
repository.WithAPPID(),
repository.WithPublishRecordID(),
repository.WithAPPPublishAtMS(),
repository.WithPublishVersion(),
repository.WithAPPPublishStatus(),
repository.WithPublishRecordExtraInfo(),
)
if err != nil {
return nil, errorx.Wrapf(err, "GetAPPAllPublishRecords failed, appID=%d", appID)
}
sort.Slice(records, func(i, j int) bool {
return records[i].APP.GetPublishedAtMS() > records[j].APP.GetPublishedAtMS()
})
for _, r := range records {
sort.Slice(r.ConnectorPublishRecords, func(i, j int) bool {
return r.ConnectorPublishRecords[i].ConnectorID < r.ConnectorPublishRecords[j].ConnectorID
})
}
return records, nil
}
func (a *appServiceImpl) GetPublishConnectorList(ctx context.Context, _ *GetPublishConnectorListRequest) (resp *GetPublishConnectorListResponse, err error) {
connectorMap, err := crossconnector.DefaultSVC().GetByIDs(ctx, entity.ConnectorIDWhiteList)
if err != nil {
return nil, errorx.Wrapf(err, "GetByIDs failed, ids=%v", entity.ConnectorIDWhiteList)
}
connectorList := make([]*connectorModel.Connector, 0, len(connectorMap))
for _, v := range connectorMap {
connectorList = append(connectorList, v)
}
sort.Slice(connectorList, func(i, j int) bool {
return connectorList[i].ID < connectorList[j].ID
})
resp = &GetPublishConnectorListResponse{
Connectors: connectorList,
}
return resp, nil
}
func (a *appServiceImpl) GetDraftAPPResources(ctx context.Context, appID int64) (resources []*entity.Resource, err error) {
plugins, err := crossplugin.DefaultSVC().GetAPPAllPlugins(ctx, appID)
if err != nil {
return nil, errorx.Wrapf(err, "GetAPPAllPlugins failed, appID=%d", appID)
}
databaseRes, err := crossdatabase.DefaultSVC().GetAllDatabaseByAppID(ctx, &databaseModel.GetAllDatabaseByAppIDRequest{
AppID: appID,
})
if err != nil {
return nil, errorx.Wrapf(err, "GetAllDatabaseByAppID failed, appID=%d", appID)
}
knowledgeRes, err := crossknowledge.DefaultSVC().ListKnowledge(ctx, &knowledgeModel.ListKnowledgeRequest{
AppID: &appID,
})
if err != nil {
return nil, errorx.Wrapf(err, "ListKnowledge failed, appID=%d", appID)
}
resources = make([]*entity.Resource, 0, len(plugins)+len(databaseRes.Databases)+len(knowledgeRes.KnowledgeList))
for _, pl := range plugins {
resources = append(resources, &entity.Resource{
ResID: pl.ID,
ResName: pl.GetName(),
ResType: entity.ResourceTypeOfPlugin,
})
}
for _, db := range databaseRes.Databases {
resources = append(resources, &entity.Resource{
ResID: db.ID,
ResName: db.TableName,
ResType: entity.ResourceTypeOfDatabase,
})
}
for _, kl := range knowledgeRes.KnowledgeList {
resources = append(resources, &entity.Resource{
ResID: kl.ID,
ResName: kl.Name,
ResType: entity.ResourceTypeOfKnowledge,
})
}
return resources, nil
}