🎨 优化扩展模块,完成ai接入和对话功能
This commit is contained in:
@@ -21,15 +21,70 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// extensionDataDir 扩展本地存储根目录
|
||||
// 与原版 SillyTavern 完全一致的路径结构:scripts/extensions/third-party/{name}/
|
||||
// 扩展 JS 中的相对路径 import(如 ../../../../../script.js)依赖此目录层级来正确解析
|
||||
// 所有 SillyTavern 核心脚本和扩展文件统一存储在 data/st-core-scripts/ 下,独立于 web-app/
|
||||
// 扩展代码是公共的(不按用户隔离),用户间差异仅在于数据库中的配置和启用状态
|
||||
const extensionDataDir = "data/st-core-scripts/scripts/extensions/third-party"
|
||||
|
||||
// getExtensionStorePath 获取扩展的本地存储路径: {extensionDataDir}/{extensionName}/
|
||||
func getExtensionStorePath(extensionName string) string {
|
||||
return filepath.Join(extensionDataDir, extensionName)
|
||||
}
|
||||
|
||||
// GetExtensionAssetLocalPath 获取扩展资源文件的本地绝对路径
|
||||
func (es *ExtensionService) GetExtensionAssetLocalPath(extensionName string, assetPath string) (string, error) {
|
||||
storePath := getExtensionStorePath(extensionName)
|
||||
fullPath := filepath.Join(storePath, assetPath)
|
||||
|
||||
// 安全检查:防止路径遍历攻击
|
||||
absStore, _ := filepath.Abs(storePath)
|
||||
absFile, _ := filepath.Abs(fullPath)
|
||||
if !strings.HasPrefix(absFile, absStore) {
|
||||
return "", errors.New("非法的资源路径")
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("资源文件不存在: %s", assetPath)
|
||||
}
|
||||
|
||||
return fullPath, nil
|
||||
}
|
||||
|
||||
// ensureExtensionDir 确保扩展存储目录存在
|
||||
func ensureExtensionDir(extensionName string) (string, error) {
|
||||
storePath := getExtensionStorePath(extensionName)
|
||||
if err := os.MkdirAll(storePath, 0755); err != nil {
|
||||
return "", fmt.Errorf("创建扩展存储目录失败: %w", err)
|
||||
}
|
||||
return storePath, nil
|
||||
}
|
||||
|
||||
// removeExtensionDir 删除扩展的本地存储目录
|
||||
func removeExtensionDir(extensionName string) error {
|
||||
storePath := getExtensionStorePath(extensionName)
|
||||
if _, err := os.Stat(storePath); os.IsNotExist(err) {
|
||||
return nil // 目录不存在,无需删除
|
||||
}
|
||||
return os.RemoveAll(storePath)
|
||||
}
|
||||
|
||||
type ExtensionService struct{}
|
||||
|
||||
// CreateExtension 创建/安装扩展
|
||||
func (es *ExtensionService) CreateExtension(userID uint, req *request.CreateExtensionRequest) (*app.AIExtension, error) {
|
||||
// 校验名称
|
||||
if req.Name == "" {
|
||||
return nil, errors.New("扩展名称不能为空")
|
||||
}
|
||||
|
||||
// 检查扩展是否已存在
|
||||
var existing app.AIExtension
|
||||
err := global.GVA_DB.Where("user_id = ? AND name = ?", userID, req.Name).First(&existing).Error
|
||||
if err == nil {
|
||||
return nil, errors.New("扩展已存在")
|
||||
return nil, fmt.Errorf("扩展 %s 已存在", req.Name)
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
@@ -136,15 +191,18 @@ func (es *ExtensionService) DeleteExtension(userID, extensionID uint, deleteFile
|
||||
return errors.New("系统内置扩展不允许删除")
|
||||
}
|
||||
|
||||
// TODO: 如果 deleteFiles=true,删除扩展文件
|
||||
// 这需要文件系统支持
|
||||
// 删除本地扩展文件(与原版 SillyTavern 一致:卸载扩展时清理本地文件)
|
||||
if err := removeExtensionDir(extension.Name); err != nil {
|
||||
global.GVA_LOG.Warn("删除扩展本地文件失败", zap.Error(err), zap.String("name", extension.Name))
|
||||
// 不阻断删除流程
|
||||
}
|
||||
|
||||
// 删除扩展(配置已经在扩展记录的 Settings 字段中,无需单独删除)
|
||||
// 删除数据库记录
|
||||
if err := global.GVA_DB.Delete(&extension).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("扩展卸载成功", zap.Uint("extensionID", extensionID))
|
||||
global.GVA_LOG.Info("扩展卸载成功", zap.Uint("extensionID", extensionID), zap.String("name", extension.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,6 +215,15 @@ func (es *ExtensionService) GetExtension(userID, extensionID uint) (*app.AIExten
|
||||
return &extension, nil
|
||||
}
|
||||
|
||||
// GetExtensionByID 通过扩展 ID 获取扩展信息(不限制用户,用于公开资源路由)
|
||||
func (es *ExtensionService) GetExtensionByID(extensionID uint) (*app.AIExtension, error) {
|
||||
var extension app.AIExtension
|
||||
if err := global.GVA_DB.Where("id = ?", extensionID).First(&extension).Error; err != nil {
|
||||
return nil, errors.New("扩展不存在")
|
||||
}
|
||||
return &extension, nil
|
||||
}
|
||||
|
||||
// GetExtensionList 获取扩展列表
|
||||
func (es *ExtensionService) GetExtensionList(userID uint, req *request.ExtensionListRequest) (*response.ExtensionListResponse, error) {
|
||||
var extensions []app.AIExtension
|
||||
@@ -550,9 +617,37 @@ func isGitURL(url string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// downloadAndInstallFromManifestURL 从 Manifest URL 下载并安装
|
||||
// GetExtensionAssetURL 根据扩展的安装来源构建资源文件的远程 URL
|
||||
func (es *ExtensionService) GetExtensionAssetURL(extension *app.AIExtension, assetPath string) (string, error) {
|
||||
if extension.SourceURL == "" {
|
||||
return "", errors.New("扩展没有源地址")
|
||||
}
|
||||
|
||||
sourceURL := strings.TrimSuffix(strings.TrimSuffix(extension.SourceURL, "/"), ".git")
|
||||
branch := extension.Branch
|
||||
if branch == "" {
|
||||
branch = "main"
|
||||
}
|
||||
|
||||
// GitLab: repo/-/raw/branch/path
|
||||
if strings.Contains(sourceURL, "gitlab.com") {
|
||||
return fmt.Sprintf("%s/-/raw/%s/%s", sourceURL, branch, assetPath), nil
|
||||
}
|
||||
// GitHub: raw.githubusercontent.com/user/repo/branch/path
|
||||
if strings.Contains(sourceURL, "github.com") {
|
||||
rawURL := strings.Replace(sourceURL, "github.com", "raw.githubusercontent.com", 1)
|
||||
return fmt.Sprintf("%s/%s/%s", rawURL, branch, assetPath), nil
|
||||
}
|
||||
// Gitee: repo/raw/branch/path
|
||||
if strings.Contains(sourceURL, "gitee.com") {
|
||||
return fmt.Sprintf("%s/raw/%s/%s", sourceURL, branch, assetPath), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", sourceURL, assetPath), nil
|
||||
}
|
||||
|
||||
// downloadAndInstallFromManifestURL 从 Manifest URL 下载并安装(同时下载资源文件到本地)
|
||||
func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manifestURL string) (*app.AIExtension, error) {
|
||||
// 创建 HTTP 客户端
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
@@ -568,7 +663,6 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
return nil, fmt.Errorf("下载 manifest.json 失败: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
manifestData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 manifest.json 失败: %w", err)
|
||||
@@ -580,21 +674,63 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
return nil, fmt.Errorf("解析 manifest.json 失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if manifest.Name == "" {
|
||||
return nil, errors.New("manifest.json 缺少 name 字段")
|
||||
// 获取有效名称
|
||||
effectiveName := manifest.GetEffectiveName()
|
||||
if effectiveName == "" {
|
||||
return nil, errors.New("manifest.json 缺少 name 或 display_name 字段")
|
||||
}
|
||||
|
||||
// 检查扩展是否已存在
|
||||
var existing app.AIExtension
|
||||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, manifest.Name).First(&existing).Error
|
||||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, effectiveName).First(&existing).Error
|
||||
if err == nil {
|
||||
return nil, fmt.Errorf("扩展 %s 已安装", manifest.Name)
|
||||
return nil, fmt.Errorf("扩展 %s 已安装", effectiveName)
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建本地存储目录并保存 manifest.json
|
||||
storePath, err := ensureExtensionDir(effectiveName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(storePath, "manifest.json"), manifestData, 0644); err != nil {
|
||||
_ = removeExtensionDir(effectiveName)
|
||||
return nil, fmt.Errorf("保存 manifest.json 失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取 manifest URL 的基础目录(用于下载关联资源)
|
||||
baseURL := manifestURL[:strings.LastIndex(manifestURL, "/")+1]
|
||||
|
||||
// 下载 JS/CSS 等资源文件到本地
|
||||
filesToDownload := []string{}
|
||||
if entry := manifest.GetEffectiveEntry(); entry != "" {
|
||||
filesToDownload = append(filesToDownload, entry)
|
||||
}
|
||||
if style := manifest.GetEffectiveStyle(); style != "" {
|
||||
filesToDownload = append(filesToDownload, style)
|
||||
}
|
||||
filesToDownload = append(filesToDownload, manifest.Assets...)
|
||||
|
||||
for _, file := range filesToDownload {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
fileURL := baseURL + file
|
||||
if err := downloadFileToLocal(client, fileURL, filepath.Join(storePath, file)); err != nil {
|
||||
global.GVA_LOG.Warn("下载扩展资源文件失败(非致命)",
|
||||
zap.String("file", file),
|
||||
zap.String("url", fileURL),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("扩展文件已保存到本地",
|
||||
zap.String("name", effectiveName),
|
||||
zap.String("path", storePath))
|
||||
|
||||
// 将 manifest 转换为 map[string]interface{}
|
||||
var manifestMap map[string]interface{}
|
||||
if err := json.Unmarshal(manifestData, &manifestMap); err != nil {
|
||||
@@ -603,13 +739,13 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
|
||||
// 构建创建请求
|
||||
createReq := &request.CreateExtensionRequest{
|
||||
Name: manifest.Name,
|
||||
Name: effectiveName,
|
||||
DisplayName: manifest.DisplayName,
|
||||
Version: manifest.Version,
|
||||
Author: manifest.Author,
|
||||
Description: manifest.Description,
|
||||
Homepage: manifest.Homepage,
|
||||
Repository: manifest.Repository, // 使用 manifest 中的 repository
|
||||
Homepage: manifest.GetEffectiveHomepage(),
|
||||
Repository: manifest.Repository,
|
||||
License: manifest.License,
|
||||
Tags: manifest.Tags,
|
||||
ExtensionType: manifest.Type,
|
||||
@@ -617,13 +753,13 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
Dependencies: manifest.Dependencies,
|
||||
Conflicts: manifest.Conflicts,
|
||||
ManifestData: manifestMap,
|
||||
ScriptPath: manifest.Entry,
|
||||
StylePath: manifest.Style,
|
||||
ScriptPath: manifest.GetEffectiveEntry(),
|
||||
StylePath: manifest.GetEffectiveStyle(),
|
||||
AssetsPaths: manifest.Assets,
|
||||
Settings: manifest.Settings,
|
||||
Options: manifest.Options,
|
||||
InstallSource: "url",
|
||||
SourceURL: manifestURL, // 记录原始 URL 用于更新
|
||||
SourceURL: manifestURL,
|
||||
AutoUpdate: manifest.AutoUpdate,
|
||||
Metadata: nil,
|
||||
}
|
||||
@@ -636,6 +772,7 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
// 创建扩展
|
||||
extension, err := es.CreateExtension(userID, createReq)
|
||||
if err != nil {
|
||||
_ = removeExtensionDir(effectiveName)
|
||||
return nil, fmt.Errorf("创建扩展失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -647,6 +784,31 @@ func (es *ExtensionService) downloadAndInstallFromManifestURL(userID uint, manif
|
||||
return extension, nil
|
||||
}
|
||||
|
||||
// downloadFileToLocal 下载远程文件到本地路径
|
||||
func downloadFileToLocal(client *http.Client, url string, localPath string) error {
|
||||
// 确保目标文件的父目录存在
|
||||
if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(localPath, data, 0644)
|
||||
}
|
||||
|
||||
// UpgradeExtension 升级扩展版本(根据安装来源自动选择更新方式)
|
||||
func (es *ExtensionService) UpgradeExtension(userID, extensionID uint, force bool) (*app.AIExtension, error) {
|
||||
// 获取扩展信息
|
||||
@@ -672,7 +834,7 @@ func (es *ExtensionService) UpgradeExtension(userID, extensionID uint, force boo
|
||||
}
|
||||
}
|
||||
|
||||
// updateExtensionFromGit 从 Git 仓库更新扩展
|
||||
// updateExtensionFromGit 从 Git 仓库更新扩展(先删除旧记录和文件,再重新安装)
|
||||
func (es *ExtensionService) updateExtensionFromGit(userID uint, extension *app.AIExtension, force bool) (*app.AIExtension, error) {
|
||||
if extension.SourceURL == "" {
|
||||
return nil, errors.New("缺少 Git 仓库 URL")
|
||||
@@ -683,11 +845,17 @@ func (es *ExtensionService) updateExtensionFromGit(userID uint, extension *app.A
|
||||
zap.String("sourceUrl", extension.SourceURL),
|
||||
zap.String("branch", extension.Branch))
|
||||
|
||||
// 重新克隆(简单方式,避免处理本地修改)
|
||||
// 先删除旧的数据库记录和本地文件
|
||||
if err := global.GVA_DB.Unscoped().Delete(extension).Error; err != nil {
|
||||
return nil, fmt.Errorf("删除旧扩展记录失败: %w", err)
|
||||
}
|
||||
_ = removeExtensionDir(extension.Name)
|
||||
|
||||
// 重新克隆安装
|
||||
return es.InstallExtensionFromGit(userID, extension.SourceURL, extension.Branch)
|
||||
}
|
||||
|
||||
// updateExtensionFromURL 从 URL 更新扩展(重新下载 manifest.json)
|
||||
// updateExtensionFromURL 从 URL 更新扩展(先删除旧记录和文件,再重新下载安装)
|
||||
func (es *ExtensionService) updateExtensionFromURL(userID uint, extension *app.AIExtension) (*app.AIExtension, error) {
|
||||
if extension.SourceURL == "" {
|
||||
return nil, errors.New("缺少 Manifest URL")
|
||||
@@ -697,18 +865,24 @@ func (es *ExtensionService) updateExtensionFromURL(userID uint, extension *app.A
|
||||
zap.String("name", extension.Name),
|
||||
zap.String("sourceUrl", extension.SourceURL))
|
||||
|
||||
// 重新下载并安装
|
||||
// 先删除旧的数据库记录和本地文件
|
||||
if err := global.GVA_DB.Unscoped().Delete(extension).Error; err != nil {
|
||||
return nil, fmt.Errorf("删除旧扩展记录失败: %w", err)
|
||||
}
|
||||
_ = removeExtensionDir(extension.Name)
|
||||
|
||||
// 重新下载安装
|
||||
return es.downloadAndInstallFromManifestURL(userID, extension.SourceURL)
|
||||
}
|
||||
|
||||
// InstallExtensionFromGit 从 Git URL 安装扩展
|
||||
// InstallExtensionFromGit 从 Git URL 安装扩展(与原版 SillyTavern 一致:将源码下载到本地)
|
||||
func (es *ExtensionService) InstallExtensionFromGit(userID uint, gitUrl, branch string) (*app.AIExtension, error) {
|
||||
// 验证 Git URL
|
||||
if !strings.Contains(gitUrl, "://") && !strings.HasSuffix(gitUrl, ".git") {
|
||||
return nil, errors.New("无效的 Git URL")
|
||||
}
|
||||
|
||||
// 创建临时目录
|
||||
// 先 clone 到临时目录读取 manifest(获取扩展名后再移动到正式目录)
|
||||
tempDir, err := os.MkdirTemp("", "extension-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建临时目录失败: %w", err)
|
||||
@@ -717,10 +891,9 @@ func (es *ExtensionService) InstallExtensionFromGit(userID uint, gitUrl, branch
|
||||
|
||||
global.GVA_LOG.Info("开始从 Git 克隆扩展",
|
||||
zap.String("gitUrl", gitUrl),
|
||||
zap.String("branch", branch),
|
||||
zap.String("tempDir", tempDir))
|
||||
zap.String("branch", branch))
|
||||
|
||||
// 执行 git clone
|
||||
// 执行 git clone(浅克隆)
|
||||
cmd := exec.Command("git", "clone", "--depth=1", "--branch="+branch, gitUrl, tempDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -744,31 +917,53 @@ func (es *ExtensionService) InstallExtensionFromGit(userID uint, gitUrl, branch
|
||||
return nil, fmt.Errorf("解析 manifest.json 失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取有效名称(兼容 SillyTavern manifest 没有 name 字段的情况)
|
||||
effectiveName := manifest.GetEffectiveName()
|
||||
if effectiveName == "" {
|
||||
return nil, errors.New("manifest.json 缺少 name 或 display_name 字段")
|
||||
}
|
||||
|
||||
// 检查扩展是否已存在
|
||||
var existing app.AIExtension
|
||||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, manifest.Name).First(&existing).Error
|
||||
err = global.GVA_DB.Where("user_id = ? AND name = ?", userID, effectiveName).First(&existing).Error
|
||||
if err == nil {
|
||||
return nil, fmt.Errorf("扩展 %s 已安装", manifest.Name)
|
||||
return nil, fmt.Errorf("扩展 %s 已安装", effectiveName)
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将扩展文件保存到公共目录: web-app/public/scripts/extensions/third-party/{extensionName}/
|
||||
storePath, err := ensureExtensionDir(effectiveName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 清空目标目录(如果有残留文件)后复制 clone 内容
|
||||
_ = os.RemoveAll(storePath)
|
||||
if err := copyDir(tempDir, storePath); err != nil {
|
||||
return nil, fmt.Errorf("保存扩展文件失败: %w", err)
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("扩展文件已保存到本地",
|
||||
zap.String("name", effectiveName),
|
||||
zap.String("path", storePath))
|
||||
|
||||
// 将 manifest 转换为 map[string]interface{}
|
||||
var manifestMap map[string]interface{}
|
||||
if err := json.Unmarshal(manifestData, &manifestMap); err != nil {
|
||||
return nil, fmt.Errorf("转换 manifest 失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建创建请求
|
||||
// 构建创建请求(使用兼容方法获取字段值)
|
||||
createReq := &request.CreateExtensionRequest{
|
||||
Name: manifest.Name,
|
||||
Name: effectiveName,
|
||||
DisplayName: manifest.DisplayName,
|
||||
Version: manifest.Version,
|
||||
Author: manifest.Author,
|
||||
Description: manifest.Description,
|
||||
Homepage: manifest.Homepage,
|
||||
Repository: manifest.Repository, // 使用 manifest 中的 repository
|
||||
Homepage: manifest.GetEffectiveHomepage(),
|
||||
Repository: manifest.Repository,
|
||||
License: manifest.License,
|
||||
Tags: manifest.Tags,
|
||||
ExtensionType: manifest.Type,
|
||||
@@ -776,28 +971,69 @@ func (es *ExtensionService) InstallExtensionFromGit(userID uint, gitUrl, branch
|
||||
Dependencies: manifest.Dependencies,
|
||||
Conflicts: manifest.Conflicts,
|
||||
ManifestData: manifestMap,
|
||||
ScriptPath: manifest.Entry,
|
||||
StylePath: manifest.Style,
|
||||
ScriptPath: manifest.GetEffectiveEntry(),
|
||||
StylePath: manifest.GetEffectiveStyle(),
|
||||
AssetsPaths: manifest.Assets,
|
||||
Settings: manifest.Settings,
|
||||
Options: manifest.Options,
|
||||
InstallSource: "git",
|
||||
SourceURL: gitUrl, // 记录 Git URL 用于更新
|
||||
Branch: branch, // 记录分支
|
||||
SourceURL: gitUrl,
|
||||
Branch: branch,
|
||||
AutoUpdate: manifest.AutoUpdate,
|
||||
Metadata: manifest.Metadata,
|
||||
}
|
||||
|
||||
// 确保扩展类型有效
|
||||
if createReq.ExtensionType == "" {
|
||||
createReq.ExtensionType = "ui"
|
||||
}
|
||||
|
||||
// 创建扩展记录
|
||||
extension, err := es.CreateExtension(userID, createReq)
|
||||
if err != nil {
|
||||
// 创建失败则清理本地文件
|
||||
_ = removeExtensionDir(effectiveName)
|
||||
return nil, fmt.Errorf("创建扩展记录失败: %w", err)
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("从 Git 安装扩展成功",
|
||||
zap.Uint("extensionID", extension.ID),
|
||||
zap.String("name", extension.Name),
|
||||
zap.String("version", extension.Version))
|
||||
zap.String("version", extension.Version),
|
||||
zap.String("localPath", storePath))
|
||||
|
||||
return extension, nil
|
||||
}
|
||||
|
||||
// copyDir 递归复制目录(排除 .git 目录以节省空间)
|
||||
func copyDir(src, dst string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算相对路径
|
||||
relPath, err := filepath.Rel(src, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 排除 .git 目录
|
||||
if info.IsDir() && info.Name() == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dst, relPath)
|
||||
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(dstPath, info.Mode())
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
srcFile, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(dstPath, srcFile, info.Mode())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user