Before Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 987 B |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
@ -1,36 +1,21 @@ |
|||||
[ |
[ |
||||
{ |
{ |
||||
"name": "在线工具", |
"name": "mysql5.7", |
||||
"url": "https://tool.lu/", |
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql-windows-5.7.7z", |
||||
"icon": "/image/store/tools.png" |
"needDownload": true, |
||||
}, |
"needInstall": true, |
||||
|
"version": "5.7.37", |
||||
|
"icon": "https://img0.baidu.com/it/u=1087008535,3349487719&fm=253&fmt=auto&app=138&f=PNG?w=969&h=500", |
||||
|
"checkProgress": true, |
||||
|
"hasRestart": true, |
||||
|
"setting": [ |
||||
{ |
{ |
||||
"name": "石墨文档", |
"path": "setting.html", |
||||
"url": "https://shimo.im/desktop", |
"lang":{ |
||||
"icon": "/image/store/shimo.png" |
"cn-zh": "设置", |
||||
}, |
"en": "Setting" |
||||
{ |
} |
||||
"name": "在线节拍器", |
} |
||||
"url": "https://metronome.tooltool.net/?utm_source=xinquji", |
] |
||||
"icon": "/image/store/jpq.png" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "查看月食", |
|
||||
"url": "https://nadc.china-vo.org/eclipse/", |
|
||||
"icon": "/image/store/yueshi.png" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "蒸 気 機", |
|
||||
"url": "https://magiconch.com/vaporwave/?from=home", |
|
||||
"icon": "/image/store/zqj.png" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "vtb 大数据", |
|
||||
"url": "https://vtbs.moe/", |
|
||||
"icon": "/image/store/vtb.png" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "更多应用", |
|
||||
"desc": "更多应用敬请期待,如果您有合适的应用,可以点击开始菜单,意见反馈取得联系" |
|
||||
} |
} |
||||
] |
] |
@ -0,0 +1,165 @@ |
|||||
|
package files |
||||
|
|
||||
|
import ( |
||||
|
"compress/bzip2" |
||||
|
"compress/gzip" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
|
||||
|
"archive/tar" |
||||
|
"archive/zip" |
||||
|
) |
||||
|
|
||||
|
// Decompress 解压文件到指定目录,支持多种压缩格式
|
||||
|
func Decompress(compressedFilePath, destPath string) error { |
||||
|
ext := filepath.Ext(compressedFilePath) |
||||
|
switch ext { |
||||
|
case ".zip": |
||||
|
return Unzip(compressedFilePath, destPath) |
||||
|
case ".tar": |
||||
|
tarFile, err := os.Open(compressedFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer tarFile.Close() |
||||
|
return Untar(tarFile, destPath) |
||||
|
case ".gz": |
||||
|
return Untargz(compressedFilePath, destPath) |
||||
|
case ".bz2": |
||||
|
return Untarbz2(compressedFilePath, destPath) |
||||
|
default: |
||||
|
return fmt.Errorf("unsupported compression format: %s", ext) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Untar 解压.tar文件到指定目录
|
||||
|
func Untar(reader io.Reader, destPath string) error { |
||||
|
tarReader := tar.NewReader(reader) |
||||
|
for { |
||||
|
header, err := tarReader.Next() |
||||
|
if err == io.EOF { |
||||
|
break |
||||
|
} |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
target := filepath.Join(destPath, header.Name) |
||||
|
if header.Typeflag == tar.TypeDir { |
||||
|
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer outFile.Close() |
||||
|
|
||||
|
if _, err := io.Copy(outFile, tarReader); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// Untargz 解压.tar.gz文件到指定目录
|
||||
|
func Untargz(targzFilePath, destPath string) error { |
||||
|
gzFile, err := os.Open(targzFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer gzFile.Close() |
||||
|
|
||||
|
gzReader, err := gzip.NewReader(gzFile) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer gzReader.Close() |
||||
|
|
||||
|
return Untar(gzReader, destPath) // 传递gzReader给Untar
|
||||
|
} |
||||
|
|
||||
|
// Untarbz2 解压.tar.bz2文件到指定目录
|
||||
|
func Untarbz2(tarbz2FilePath, destPath string) error { |
||||
|
bz2File, err := os.Open(tarbz2FilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer bz2File.Close() |
||||
|
|
||||
|
bz2Reader := bzip2.NewReader(bz2File) |
||||
|
return Untar(bz2Reader, destPath) // 传递bz2Reader给Untar
|
||||
|
} |
||||
|
|
||||
|
// DecompressZip 解压zip文件到指定目录
|
||||
|
func Unzip(zipFilePath, destPath string) error { |
||||
|
r, err := zip.OpenReader(zipFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer r.Close() |
||||
|
|
||||
|
for _, f := range r.File { |
||||
|
rc, err := f.Open() |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer rc.Close() |
||||
|
|
||||
|
path := filepath.Join(destPath, f.Name) |
||||
|
|
||||
|
if f.FileInfo().IsDir() { |
||||
|
os.MkdirAll(path, f.Mode()) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
os.MkdirAll(filepath.Dir(path), f.Mode()) |
||||
|
|
||||
|
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer outFile.Close() |
||||
|
|
||||
|
_, err = io.Copy(outFile, rc) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
|
||||
|
func IsSupportedCompressionFormat(filename string) bool { |
||||
|
supportedFormats := map[string]bool{ |
||||
|
".zip": true, |
||||
|
".tar": true, |
||||
|
".gz": true, |
||||
|
".bz2": true, |
||||
|
} |
||||
|
|
||||
|
for format := range supportedFormats { |
||||
|
if strings.HasSuffix(strings.ToLower(filename), format) { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
|
||||
|
func HandlerFile(filePath string, destDir string) error { |
||||
|
if !IsSupportedCompressionFormat(filePath) { |
||||
|
// 移动文件
|
||||
|
newPath := filepath.Join(destDir, filepath.Base(filePath)) |
||||
|
return os.Rename(filePath, newPath) |
||||
|
} |
||||
|
// 解压文件
|
||||
|
return Decompress(filePath, destDir) |
||||
|
} |
@ -0,0 +1,219 @@ |
|||||
|
package files |
||||
|
|
||||
|
import ( |
||||
|
"archive/tar" |
||||
|
"archive/zip" |
||||
|
"compress/gzip" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// 压缩文件到指定的目录
|
||||
|
func zipCompress(folderPath, zipFilePath string) error { |
||||
|
zipFile, err := os.Create(zipFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer zipFile.Close() |
||||
|
|
||||
|
zipWriter := zip.NewWriter(zipFile) |
||||
|
defer zipWriter.Close() |
||||
|
|
||||
|
info, err := os.Stat(folderPath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
var baseDir string |
||||
|
if info.IsDir() { |
||||
|
baseDir = filepath.Base(folderPath) |
||||
|
} |
||||
|
|
||||
|
filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
header, err := zip.FileInfoHeader(info) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
relPath, err := filepath.Rel(folderPath, path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if baseDir != "" { |
||||
|
header.Name = filepath.Join(baseDir, relPath) |
||||
|
} else { |
||||
|
header.Name = relPath |
||||
|
} |
||||
|
|
||||
|
if info.IsDir() { |
||||
|
header.Name += "/" |
||||
|
} else { |
||||
|
header.Method = zip.Deflate |
||||
|
} |
||||
|
|
||||
|
writer, err := zipWriter.CreateHeader(header) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if info.IsDir() { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
file, err := os.Open(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
_, err = io.Copy(writer, file) |
||||
|
return err |
||||
|
}) |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
|
||||
|
func IsSupportedFormat(filename string) bool { |
||||
|
supportedFormats := map[string]bool{ |
||||
|
".zip": true, |
||||
|
".tar": true, |
||||
|
".gz": true, |
||||
|
".bz2": true, |
||||
|
// 添加其他支持的复合格式
|
||||
|
} |
||||
|
|
||||
|
for format := range supportedFormats { |
||||
|
if strings.HasSuffix(strings.ToLower(filename), format) { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// CompressFileOrFolder 压缩文件或文件夹到指定的压缩文件
|
||||
|
func CompressFileOrFolder(sourcePath, compressedFilePath string) error { |
||||
|
if IsSupportedFormat(compressedFilePath) { |
||||
|
return Encompress(sourcePath, compressedFilePath) |
||||
|
} |
||||
|
// 如果压缩文件格式不受支持,则返回错误或采取其他措施
|
||||
|
return fmt.Errorf("unsupported compression format for file: %s", compressedFilePath) |
||||
|
} |
||||
|
|
||||
|
// Encompress 压缩文件或文件夹到指定的压缩格式
|
||||
|
func Encompress(sourcePath, compressedFilePath string) error { |
||||
|
switch filepath.Ext(compressedFilePath) { |
||||
|
case ".zip": |
||||
|
return zipCompress(sourcePath, compressedFilePath) |
||||
|
case ".tar": |
||||
|
return tarCompress(sourcePath, compressedFilePath) |
||||
|
case ".gz": |
||||
|
return tarGzCompress(sourcePath, compressedFilePath) |
||||
|
default: |
||||
|
return fmt.Errorf("unsupported compression format: %s", filepath.Ext(compressedFilePath)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// tarCompress 使用 tar 格式压缩文件或文件夹
|
||||
|
func tarCompress(folderPath, tarFilePath string) error { |
||||
|
// 打开目标文件
|
||||
|
tarFile, err := os.Create(tarFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer tarFile.Close() |
||||
|
|
||||
|
// 创建 tar.Writer
|
||||
|
tarWriter := tar.NewWriter(tarFile) |
||||
|
defer tarWriter.Close() |
||||
|
|
||||
|
// 遍历文件夹并添加到 tar 文件
|
||||
|
err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
header, err := tar.FileInfoHeader(info, "") |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if err := tarWriter.WriteHeader(header); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if !info.IsDir() { |
||||
|
file, err := os.Open(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
_, err = io.Copy(tarWriter, file) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
|
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// tarGzCompress 使用 tar.gz 格式压缩文件或文件夹
|
||||
|
func tarGzCompress(folderPath, tarGzFilePath string) error { |
||||
|
// 打开目标文件
|
||||
|
gzFile, err := os.Create(tarGzFilePath) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer gzFile.Close() |
||||
|
|
||||
|
// 创建 gzip.Writer
|
||||
|
gzWriter := gzip.NewWriter(gzFile) |
||||
|
defer gzWriter.Close() |
||||
|
|
||||
|
// 创建 tar.Writer
|
||||
|
tarWriter := tar.NewWriter(gzWriter) |
||||
|
defer tarWriter.Close() |
||||
|
|
||||
|
// 遍历文件夹并添加到 tar 文件
|
||||
|
err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
header, err := tar.FileInfoHeader(info, "") |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if err := tarWriter.WriteHeader(header); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
if !info.IsDir() { |
||||
|
file, err := os.Open(path) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
_, err = io.Copy(tarWriter, file) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
|
||||
|
return err |
||||
|
} |
@ -1,97 +0,0 @@ |
|||||
package progress |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"log" |
|
||||
"net/http" |
|
||||
"net/url" |
|
||||
"path" |
|
||||
"time" |
|
||||
|
|
||||
"github.com/gorilla/mux" |
|
||||
) |
|
||||
|
|
||||
func ForwardRequest(w http.ResponseWriter, r *http.Request) { |
|
||||
vars := mux.Vars(r) |
|
||||
proxyName := vars["name"] // 获取代理名称
|
|
||||
log.Printf("Forwarding request to proxy %s", proxyName) |
|
||||
log.Printf("processes: %+v", processes) |
|
||||
cmd, ok := processes[proxyName] |
|
||||
if !ok || !cmd.Running { |
|
||||
// Start the process again
|
|
||||
err := ExecuteScript(proxyName) |
|
||||
if err != nil { |
|
||||
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart process %s: %v", proxyName, err)) |
|
||||
return |
|
||||
} |
|
||||
time.Sleep(1 * time.Second) |
|
||||
} |
|
||||
if cmd.PingURL == "" { |
|
||||
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get url %s", proxyName)) |
|
||||
return |
|
||||
} |
|
||||
// 构造目标基础URL,假设proxyName直接对应目标服务的基础路径
|
|
||||
targetBaseURLStr := cmd.PingURL |
|
||||
targetBaseURL, err := url.Parse(targetBaseURLStr) |
|
||||
if err != nil { |
|
||||
respondWithError(w, http.StatusInternalServerError, "Failed to parse target base URL") |
|
||||
return |
|
||||
} |
|
||||
// 获取请求的子路径
|
|
||||
subpath := r.URL.Path[len("/"+proxyName):] |
|
||||
|
|
||||
targetURL := &url.URL{ |
|
||||
Scheme: targetBaseURL.Scheme, |
|
||||
Host: targetBaseURL.Host, |
|
||||
Path: path.Clean(path.Join(targetBaseURL.Path, subpath)), // 使用path.Clean处理路径
|
|
||||
RawQuery: r.URL.RawQuery, // 正确编码查询参数
|
|
||||
} |
|
||||
log.Printf("Forwarding request to %s", targetURL) |
|
||||
|
|
||||
// 执行重定向
|
|
||||
http.Redirect(w, r, targetURL.String(), http.StatusTemporaryRedirect) |
|
||||
/* |
|
||||
|
|
||||
|
|
||||
|
|
||||
// 保留原请求的路径和查询字符串,构建完整的目标URL
|
|
||||
// 如果没有子路径,直接使用基础URL
|
|
||||
// 如果有子路径,将其附加到基础URL
|
|
||||
|
|
||||
client := http.DefaultClient |
|
||||
req, err := http.NewRequest(r.Method, targetURL.String(), r.Body) |
|
||||
if err != nil { |
|
||||
respondWithError(w, http.StatusBadRequest, "Failed to create request: "+err.Error()) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
copyHeader(req.Header, r.Header) |
|
||||
|
|
||||
resp, err := client.Do(req) |
|
||||
if err != nil { |
|
||||
respondWithError(w, http.StatusServiceUnavailable, "Failed to forward request: "+err.Error()) |
|
||||
return |
|
||||
} |
|
||||
defer resp.Body.Close() |
|
||||
|
|
||||
// 将响应头复制到w.Header()
|
|
||||
for k, vv := range resp.Header { |
|
||||
for _, v := range vv { |
|
||||
w.Header().Add(k, v) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
w.WriteHeader(resp.StatusCode) |
|
||||
if _, err := io.Copy(w, resp.Body); err != nil { |
|
||||
log.Printf("Error copying response body: %v", err) |
|
||||
}*/ |
|
||||
} |
|
||||
|
|
||||
// 复制源Header到目标Header
|
|
||||
// func copyHeader(dst, src http.Header) {
|
|
||||
// for k, vv := range src {
|
|
||||
// for _, v := range vv {
|
|
||||
// dst.Add(k, v)
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
@ -1,159 +1,156 @@ |
|||||
package store |
package store |
||||
|
|
||||
import ( |
import ( |
||||
"context" |
|
||||
"encoding/json" |
"encoding/json" |
||||
|
"godo/files" |
||||
"godo/libs" |
"godo/libs" |
||||
"io" |
"io" |
||||
"log" |
"log" |
||||
"net/http" |
"net/http" |
||||
|
"os" |
||||
"path/filepath" |
"path/filepath" |
||||
"sync" |
|
||||
"time" |
"time" |
||||
|
|
||||
"github.com/cavaliergopher/grab/v3" |
|
||||
) |
) |
||||
|
|
||||
|
type ProgressReader struct { |
||||
|
reader io.Reader |
||||
|
total int64 |
||||
|
err error |
||||
|
} |
||||
|
|
||||
type DownloadStatus struct { |
type DownloadStatus struct { |
||||
resp *grab.Response |
|
||||
cancel context.CancelFunc |
|
||||
Name string `json:"name"` |
Name string `json:"name"` |
||||
Path string `json:"path"` |
Path string `json:"path"` |
||||
Url string `json:"url"` |
Url string `json:"url"` |
||||
Current int64 `json:"current"` |
Current int64 `json:"current"` |
||||
Size int64 `json:"size"` |
Size int64 `json:"size"` |
||||
Speed float64 `json:"speed"` |
Speed float64 `json:"speed"` |
||||
Progress float64 `json:"progress"` |
Progress int `json:"progress"` |
||||
Downloading bool `json:"downloading"` |
Downloading bool `json:"downloading"` |
||||
Done bool `json:"done"` |
Done bool `json:"done"` |
||||
} |
} |
||||
|
|
||||
const ( |
func (pr *ProgressReader) Read(p []byte) (n int, err error) { |
||||
concurrency = 6 // 并发下载数
|
n, err = pr.reader.Read(p) |
||||
) |
pr.err = err |
||||
|
pr.total += int64(n) |
||||
// var downloads = make(map[string]*grab.Response)
|
return |
||||
var downloadsMutex sync.Mutex |
|
||||
var downloadList map[string]*DownloadStatus |
|
||||
|
|
||||
func existsInDownloadList(url string) bool { |
|
||||
_, ok := downloadList[url] |
|
||||
return ok |
|
||||
} |
} |
||||
|
|
||||
func PauseDownload(url string) { |
func DownloadHandler(w http.ResponseWriter, r *http.Request) { |
||||
downloadsMutex.Lock() |
url := r.URL.Query().Get("url") |
||||
defer downloadsMutex.Unlock() |
log.Printf("Download url: %s", url) |
||||
ds, ok := downloadList[url] |
|
||||
if ds.Url == url && ok { |
|
||||
if ds.cancel != nil { |
|
||||
ds.cancel() |
|
||||
} |
|
||||
ds.resp = nil |
|
||||
ds.Downloading = false |
|
||||
ds.Speed = 0 |
|
||||
} |
|
||||
|
|
||||
} |
// 获取下载目录,这里假设从请求参数中获取,如果没有则使用默认值
|
||||
|
downloadDir := libs.GetCacheDir() |
||||
|
if downloadDir == "" { |
||||
|
downloadDir = "./downloads" |
||||
|
} |
||||
|
|
||||
func ContinueDownload(url string) { |
// 拼接完整的文件路径
|
||||
ds, ok := downloadList[url] |
fileName := filepath.Base(url) |
||||
if ds.Url == url && ok { |
filePath := filepath.Join(downloadDir, fileName) |
||||
if !ds.Downloading && ds.resp == nil && !ds.Done { |
|
||||
ds.Downloading = true |
|
||||
|
|
||||
req, err := grab.NewRequest(ds.Path, ds.Url) |
// 开始下载
|
||||
|
resp, err := http.Get(url) |
||||
if err != nil { |
if err != nil { |
||||
ds.Downloading = false |
log.Printf("Failed to get file: %v", err) |
||||
|
http.Error(w, "Failed to get file", http.StatusInternalServerError) |
||||
return |
return |
||||
} |
} |
||||
ctx, cancel := context.WithCancel(context.Background()) |
defer resp.Body.Close() |
||||
ds.cancel = cancel |
|
||||
req = req.WithContext(ctx) |
|
||||
client := grab.NewClient() |
|
||||
client.HTTPClient = &http.Client{ |
|
||||
Transport: &http.Transport{ |
|
||||
MaxIdleConnsPerHost: concurrency, // 设置并发连接数
|
|
||||
}, |
|
||||
} |
|
||||
//resp := grab.DefaultClient.Do(req)
|
|
||||
resp := client.Do(req) |
|
||||
|
|
||||
if resp != nil && resp.HTTPResponse != nil && |
// 检查文件是否已存在且大小一致
|
||||
resp.HTTPResponse.StatusCode >= 200 && resp.HTTPResponse.StatusCode < 300 { |
if fileInfo, err := os.Stat(filePath); err == nil { |
||||
ds.resp = resp |
if fileInfo.Size() == resp.ContentLength { |
||||
|
// 文件已存在且大小一致,无需下载
|
||||
|
runDir := libs.GetRunDir() |
||||
|
err := files.HandlerFile(filePath, runDir) |
||||
|
if err != nil { |
||||
|
log.Printf("Error moving file: %v", err) |
||||
|
} |
||||
|
libs.SuccessMsg(w, "success", "File already exists and is of correct size") |
||||
|
return |
||||
} else { |
} else { |
||||
ds.Downloading = false |
// 重新打开响应体以便后续读取
|
||||
|
resp.Body = http.NoBody |
||||
|
resp, err = http.Get(url) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to get file: %v", err) |
||||
|
http.Error(w, "Failed to get file", http.StatusInternalServerError) |
||||
|
return |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
} |
// 创建文件
|
||||
|
file, err := os.Create(filePath) |
||||
func Download(url string) { |
if err != nil { |
||||
chacheDir := libs.GetCacheDir() |
log.Printf("Failed to create file: %v", err) |
||||
absPath := filepath.Join(chacheDir, filepath.Base(url)) |
http.Error(w, "Failed to create file", http.StatusInternalServerError) |
||||
if !existsInDownloadList(url) { |
return |
||||
downloadList[url] = &DownloadStatus{ |
|
||||
resp: nil, |
|
||||
Name: filepath.Base(url), |
|
||||
Path: absPath, |
|
||||
Url: url, |
|
||||
Downloading: false, |
|
||||
} |
|
||||
} |
|
||||
ContinueDownload(url) |
|
||||
} |
|
||||
func GetDownload(url string) *DownloadStatus { |
|
||||
downloadsMutex.Lock() |
|
||||
defer downloadsMutex.Unlock() |
|
||||
ds, ok := downloadList[url] |
|
||||
if ds.resp != nil && ok { |
|
||||
ds.Current = ds.resp.BytesComplete() |
|
||||
ds.Size = ds.resp.Size() |
|
||||
ds.Speed = ds.resp.BytesPerSecond() |
|
||||
ds.Progress = 100 * ds.resp.Progress() |
|
||||
ds.Downloading = !ds.resp.IsComplete() |
|
||||
ds.Done = ds.resp.Progress() == 1 |
|
||||
if !ds.Downloading { |
|
||||
ds.resp = nil |
|
||||
} |
|
||||
} |
|
||||
if ds.Done { |
|
||||
delete(downloadList, url) |
|
||||
} |
} |
||||
return ds |
defer file.Close() |
||||
} |
|
||||
func DownloadHandler(w http.ResponseWriter, r *http.Request) { |
// 使用ProgressReader来跟踪进度
|
||||
url := r.URL.Query().Get("url") |
pr := &ProgressReader{reader: resp.Body} |
||||
Download(url) |
|
||||
ticker := time.NewTicker(500 * time.Millisecond) |
// 启动定时器来报告进度
|
||||
|
ticker := time.NewTicker(200 * time.Millisecond) |
||||
defer ticker.Stop() |
defer ticker.Stop() |
||||
|
|
||||
flusher, ok := w.(http.Flusher) |
flusher, ok := w.(http.Flusher) |
||||
if !ok { |
if !ok { |
||||
log.Printf("Streaming unsupported") |
log.Printf("Streaming unsupported") |
||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError) |
http.Error(w, "Streaming unsupported", http.StatusInternalServerError) |
||||
return |
return |
||||
} |
} |
||||
|
|
||||
go func() { |
go func() { |
||||
for { |
for { |
||||
<-ticker.C |
<-ticker.C |
||||
ds := GetDownload(url) |
rp := &DownloadStatus{ |
||||
jsonBytes, err := json.Marshal(ds) |
Name: fileName, |
||||
|
Path: filePath, |
||||
|
Url: url, |
||||
|
Current: pr.total, |
||||
|
Size: resp.ContentLength, |
||||
|
Speed: 0, // 这里可以计算速度,但为了简化示例,我们暂时设为0
|
||||
|
Progress: int(100 * (float64(pr.total) / float64(resp.ContentLength))), |
||||
|
Downloading: pr.err == nil && pr.total < resp.ContentLength, |
||||
|
Done: pr.total == resp.ContentLength, |
||||
|
} |
||||
|
if pr.err != nil || rp.Done { |
||||
|
rp.Downloading = false |
||||
|
//log.Printf("Download complete: %s", filePath)
|
||||
|
runDir := libs.GetRunDir() |
||||
|
err := files.HandlerFile(filePath, runDir) |
||||
if err != nil { |
if err != nil { |
||||
log.Printf("Error marshaling FileProgress to JSON: %v", err) |
log.Printf("Error moving file: %v", err) |
||||
continue |
} |
||||
|
break |
||||
} |
} |
||||
if w != nil { |
if w != nil { |
||||
io.WriteString(w, string(jsonBytes)) |
jsonBytes, err := json.Marshal(rp) |
||||
|
if err != nil { |
||||
|
log.Printf("Error marshaling DownloadStatus to JSON: %v", err) |
||||
|
continue |
||||
|
} |
||||
|
w.Write(jsonBytes) |
||||
w.Write([]byte("\n")) |
w.Write([]byte("\n")) |
||||
flusher.Flush() |
flusher.Flush() |
||||
} else { |
} else { |
||||
log.Println("ResponseWriter is nil, cannot send progress") |
log.Println("ResponseWriter is nil, cannot send progress") |
||||
} |
} |
||||
if ds.Done { |
|
||||
return |
|
||||
} |
|
||||
} |
} |
||||
}() |
}() |
||||
|
|
||||
|
// 将响应体的内容写入文件
|
||||
|
_, err = io.Copy(file, pr) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to write file: %v", err) |
||||
|
http.Error(w, "Failed to write file", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
libs.SuccessMsg(w, "success", "Download complete") |
||||
} |
} |
||||
|
@ -0,0 +1,401 @@ |
|||||
|
package store |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"godo/files" |
||||
|
"godo/libs" |
||||
|
"godo/progress" |
||||
|
"log" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"os/exec" |
||||
|
"path/filepath" |
||||
|
"regexp" |
||||
|
"runtime" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
type StoreInfo struct { |
||||
|
Name string `json:"name"` |
||||
|
URL string `json:"url"` |
||||
|
NeedDownload bool `json:"needDownload"` |
||||
|
Icon string `json:"icon"` |
||||
|
Setting Setting `json:"setting"` |
||||
|
Config map[string]any `json:"config"` |
||||
|
Cmds map[string][]Cmd `json:"cmds"` |
||||
|
Install Install `json:"install"` |
||||
|
Start Install `json:"start"` |
||||
|
} |
||||
|
type Setting struct { |
||||
|
BinPath string `json:"binPath"` |
||||
|
ConfPath string `json:"confPath"` |
||||
|
DataDir string `json:"dataDir"` |
||||
|
LogDir string `json:"logDir"` |
||||
|
} |
||||
|
|
||||
|
type Item struct { |
||||
|
Name string `json:"name"` |
||||
|
Value any `json:"value"` |
||||
|
} |
||||
|
type Cmd struct { |
||||
|
Name string `json:"name"` |
||||
|
FilePath string `json:"filePath,omitempty"` |
||||
|
Content string `json:"content,omitempty"` |
||||
|
BinPath string `json:"binPath,omitempty"` |
||||
|
Cmds []string `json:"cmds,omitempty"` |
||||
|
Waiting bool `json:"waiting"` |
||||
|
Envs []Item `json:"envs"` |
||||
|
} |
||||
|
|
||||
|
type Install struct { |
||||
|
Envs []Item `json:"envs"` |
||||
|
Cmds []string `json:"cmds"` |
||||
|
} |
||||
|
|
||||
|
func InstallHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
pluginName := r.URL.Query().Get("name") |
||||
|
if pluginName == "" { |
||||
|
libs.ErrorMsg(w, "the app name is empty!") |
||||
|
return |
||||
|
} |
||||
|
exeDir := libs.GetRunDir() |
||||
|
exePath := filepath.Join(exeDir, pluginName) |
||||
|
//log.Printf("the app path is %s", exePath)
|
||||
|
if !libs.PathExists(exePath) { |
||||
|
libs.ErrorMsg(w, "the app path is not exists!") |
||||
|
return |
||||
|
} |
||||
|
installFile := filepath.Join(exePath, "install.json") |
||||
|
if !libs.PathExists(installFile) { |
||||
|
libs.ErrorMsg(w, "the install.json is not exists!") |
||||
|
return |
||||
|
} |
||||
|
var storeInfo StoreInfo |
||||
|
content, err := os.ReadFile(installFile) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "cant read the install.json!") |
||||
|
return |
||||
|
} |
||||
|
//设置 info.json
|
||||
|
exePath = strings.ReplaceAll(exePath, "\\", "/") |
||||
|
contentBytes := []byte(strings.ReplaceAll(string(content), "{exePath}", exePath)) |
||||
|
//log.Printf("====content: %s", string(contentBytes))
|
||||
|
err = json.Unmarshal(contentBytes, &storeInfo) |
||||
|
if err != nil { |
||||
|
log.Printf("Error during unmarshal: %v", err) |
||||
|
libs.ErrorMsg(w, "the install.json is error: "+err.Error()) |
||||
|
return |
||||
|
} |
||||
|
replacePlaceholdersInCmds(&storeInfo) |
||||
|
infoFile := filepath.Join(exePath, "info.json") |
||||
|
err = SaveStoreInfo(storeInfo, infoFile) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "save the info.json is error!") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
//设置config
|
||||
|
if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") { |
||||
|
err = SaveStoreConfig(storeInfo, exePath) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "save the config is error!") |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
err = InstallStore(storeInfo) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "install the app is error!") |
||||
|
return |
||||
|
} |
||||
|
//复制static目录
|
||||
|
staticPath := filepath.Join(exePath, "static") |
||||
|
if libs.PathExists(staticPath) { |
||||
|
staticDir := libs.GetStaticDir() |
||||
|
targetPath := filepath.Join(staticDir, pluginName) |
||||
|
err = os.Rename(staticPath, targetPath) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "copy the static is error!") |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
libs.SuccessMsg(w, "success", "install the app success!") |
||||
|
} |
||||
|
func UnInstallHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
pluginName := r.URL.Query().Get("name") |
||||
|
if pluginName == "" { |
||||
|
libs.ErrorMsg(w, "the app name is empty!") |
||||
|
return |
||||
|
} |
||||
|
err := progress.StopCmd(pluginName) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "stop the app is error!") |
||||
|
return |
||||
|
} |
||||
|
exeDir := libs.GetRunDir() |
||||
|
exePath := filepath.Join(exeDir, pluginName) |
||||
|
//log.Printf("the app path is %s", exePath)
|
||||
|
if libs.PathExists(exePath) { |
||||
|
err := os.RemoveAll(exePath) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "delete the app is error!") |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
staticDir := libs.GetStaticDir() |
||||
|
staticPath := filepath.Join(staticDir, pluginName) |
||||
|
if libs.PathExists(staticPath) { |
||||
|
err := os.RemoveAll(staticPath) |
||||
|
if err != nil { |
||||
|
libs.ErrorMsg(w, "delete the static is error!") |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
libs.SuccessMsg(w, "success", "uninstall the app success!") |
||||
|
|
||||
|
} |
||||
|
func convertValueToString(value any) string { |
||||
|
var strValue string |
||||
|
switch v := value.(type) { |
||||
|
case string: |
||||
|
strValue = v |
||||
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: |
||||
|
strValue = fmt.Sprintf("%v", v) |
||||
|
default: |
||||
|
strValue = fmt.Sprintf("%v", v) // 处理其他类型,例如布尔型或更复杂的类型
|
||||
|
} |
||||
|
return strValue |
||||
|
} |
||||
|
|
||||
|
func InstallStore(storeInfo StoreInfo) error { |
||||
|
err := SetEnvs(storeInfo.Install.Envs) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to set install environment variable %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
if len(storeInfo.Install.Cmds) > 0 { |
||||
|
for _, cmdKey := range storeInfo.Install.Cmds { |
||||
|
if _, ok := storeInfo.Cmds[cmdKey]; ok { |
||||
|
// 如果命令存在,你可以进一步处理 cmds
|
||||
|
RunCmds(storeInfo, cmdKey) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func RunCmds(storeInfo StoreInfo, cmdKey string) error { |
||||
|
cmds := storeInfo.Cmds[cmdKey] |
||||
|
for _, cmd := range cmds { |
||||
|
|
||||
|
if cmd.Name == "stop" { |
||||
|
runStop(storeInfo) |
||||
|
} |
||||
|
if cmd.Name == "start" { |
||||
|
runStart(storeInfo) |
||||
|
} |
||||
|
if cmd.Name == "restart" { |
||||
|
runRestart(storeInfo) |
||||
|
} |
||||
|
if cmd.Name == "exec" { |
||||
|
runExec(storeInfo, cmd) |
||||
|
} |
||||
|
if cmd.Name == "writeFile" { |
||||
|
err := WriteFile(cmd) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to write to file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
if cmd.Name == "deleteFile" { |
||||
|
err := DeleteFile(cmd) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to delete file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
if cmd.Name == "unzip" { |
||||
|
err := Unzip(cmd) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to unzip file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
if cmd.Name == "zip" { |
||||
|
err := Zip(cmd) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to unzip file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
if cmd.Name == "mkdir" { |
||||
|
err := MkDir(cmd) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to create directory: %w", err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func runStop(storeInfo StoreInfo) error { |
||||
|
return progress.StopCmd(storeInfo.Name) |
||||
|
} |
||||
|
func runStart(storeInfo StoreInfo) error { |
||||
|
err := SetEnvs(storeInfo.Start.Envs) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
if len(storeInfo.Start.Cmds) > 0 { |
||||
|
if !libs.PathExists(storeInfo.Setting.BinPath) { |
||||
|
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath) |
||||
|
} |
||||
|
cmd := exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...) |
||||
|
if runtime.GOOS == "windows" { |
||||
|
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
|
cmd = progress.SetHideConsoleCursor(cmd) |
||||
|
} |
||||
|
// 启动脚本命令并返回可能的错误
|
||||
|
if err := cmd.Start(); err != nil { |
||||
|
return fmt.Errorf("failed to start process %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
|
||||
|
progress.RegisterProcess(storeInfo.Name, cmd) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func runRestart(storeInfo StoreInfo) error { |
||||
|
err := runStop(storeInfo) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to stop process %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
return runStart(storeInfo) |
||||
|
} |
||||
|
func runExec(storeInfo StoreInfo, cmdParam Cmd) error { |
||||
|
err := SetEnvs(cmdParam.Envs) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
log.Printf("bin path:%v", cmdParam.BinPath) |
||||
|
log.Printf("cmds:%v", cmdParam.Cmds) |
||||
|
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...) |
||||
|
if runtime.GOOS == "windows" { |
||||
|
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
|
cmd = progress.SetHideConsoleCursor(cmd) |
||||
|
} |
||||
|
if err := cmd.Start(); err != nil { |
||||
|
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
if cmdParam.Waiting { |
||||
|
if err = cmd.Wait(); err != nil { |
||||
|
return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err) |
||||
|
} |
||||
|
} |
||||
|
log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name) |
||||
|
return nil |
||||
|
} |
||||
|
func WriteFile(cmd Cmd) error { |
||||
|
if cmd.FilePath != "" { |
||||
|
content := cmd.Content |
||||
|
if content != "" { |
||||
|
err := os.WriteFile(cmd.FilePath, []byte(content), 0644) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to write to file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func DeleteFile(cmd Cmd) error { |
||||
|
if cmd.FilePath != "" { |
||||
|
err := os.Remove(cmd.FilePath) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to delete file: %w", err) |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func MkDir(cmd Cmd) error { |
||||
|
err := os.MkdirAll(cmd.FilePath, os.ModePerm) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to make dir: %w", err) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func Unzip(cmd Cmd) error { |
||||
|
return files.Decompress(cmd.FilePath, cmd.Content) |
||||
|
} |
||||
|
func Zip(cmd Cmd) error { |
||||
|
return files.Encompress(cmd.FilePath, cmd.Content) |
||||
|
} |
||||
|
func SetEnvs(envs []Item) error { |
||||
|
if len(envs) > 0 { |
||||
|
for _, item := range envs { |
||||
|
strValue := convertValueToString(item.Value) |
||||
|
if strValue != "" { |
||||
|
err := os.Setenv(item.Name, strValue) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to set environment variable %s: %w", item.Name, err) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func SaveStoreInfo(storeInfo StoreInfo, infoFile string) error { |
||||
|
// 使用 json.MarshalIndent 直接获取内容的字节切片
|
||||
|
content, err := json.MarshalIndent(storeInfo, "", " ") |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal reqBodies to JSON: %w", err) |
||||
|
} |
||||
|
if err := os.WriteFile(infoFile, content, 0644); err != nil { |
||||
|
return fmt.Errorf("failed to write to file: %w", err) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func SaveStoreConfig(storeInfo StoreInfo, exePath string) error { |
||||
|
content, err := os.ReadFile(storeInfo.Setting.ConfPath + ".tpl") |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to read tpl file: %w", err) |
||||
|
} |
||||
|
contentstr := strings.ReplaceAll(string(content), "{exePath}", exePath) |
||||
|
contentstr = ChangeConfig(storeInfo, contentstr) |
||||
|
if err := os.WriteFile(storeInfo.Setting.ConfPath, []byte(contentstr), 0644); err != nil { |
||||
|
return fmt.Errorf("failed to write to file: %w", err) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func ChangeConfig(storeInfo StoreInfo, contentstr string) string { |
||||
|
pattern := regexp.MustCompile(`\{(\w+)\}`) |
||||
|
for key, value := range storeInfo.Config { |
||||
|
strValue := convertValueToString(value) |
||||
|
contentstr = pattern.ReplaceAllStringFunc(contentstr, func(match string) string { |
||||
|
if strings.Trim(match, "{}") == key { |
||||
|
return strValue |
||||
|
} |
||||
|
return match |
||||
|
}) |
||||
|
} |
||||
|
return contentstr |
||||
|
} |
||||
|
|
||||
|
func replacePlaceholdersInCmds(storeInfo *StoreInfo) { |
||||
|
// 创建一个正则表达式模式,用于匹配形如{key}的占位符
|
||||
|
pattern := regexp.MustCompile(`\{(\w+)\}`) |
||||
|
|
||||
|
// 遍历Config中的所有键值对
|
||||
|
for key, value := range storeInfo.Config { |
||||
|
// 遍历Cmds中的所有命令组
|
||||
|
for cmdGroupName, cmds := range storeInfo.Cmds { |
||||
|
// 遍历每个命令组中的所有Cmd
|
||||
|
for i, cmd := range cmds { |
||||
|
strValue := convertValueToString(value) |
||||
|
// 使用正则表达式替换Content中的占位符
|
||||
|
storeInfo.Cmds[cmdGroupName][i].Content = pattern.ReplaceAllStringFunc( |
||||
|
cmd.Content, |
||||
|
func(match string) string { |
||||
|
// 检查占位符是否与Config中的键匹配
|
||||
|
if strings.Trim(match, "{}") == key { |
||||
|
return strValue |
||||
|
} |
||||
|
return match |
||||
|
}, |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package sys |
package store |
||||
|
|
||||
import ( |
import ( |
||||
"encoding/json" |
"encoding/json" |