diff --git a/.github/workflows/backend-ci-cd.yml b/.github/workflows/backend-ci-cd.yml index a3f21b51..96f66381 100644 --- a/.github/workflows/backend-ci-cd.yml +++ b/.github/workflows/backend-ci-cd.yml @@ -99,6 +99,9 @@ jobs: VERSION_NO_V=${{ steps.get_version.outputs.VERSION }} VERSION_NO_V=${VERSION_NO_V#v} wget -O assets/vsix/monkeycode-${VERSION_NO_V}.vsix https://baizhiyun.oss-cn-hangzhou.aliyuncs.com/monkeycode/vsix/monkeycode-${VERSION_NO_V}.vsix + + # 下载 JetBrains 插件 (架构无关), jetbrains插件的下载地址硬编码, 后续需要修改 + wget -O assets/jetbrains/monkeycode-${VERSION_NO_V}.zip https://baizhiyun.oss-cn-hangzhou.aliyuncs.com/monkeycode/jetbrains/monkeycode-jetbrains-1.22.4.zip # 下载 x86_64 SGP wget -O tarballs/sgp.tgz https://baizhiyun.oss-cn-hangzhou.aliyuncs.com/monkeycode/sgp/x86_64/sgp.tgz diff --git a/backend/build/Dockerfile b/backend/build/Dockerfile index 4a768c8b..0fa22bbc 100644 --- a/backend/build/Dockerfile +++ b/backend/build/Dockerfile @@ -27,6 +27,7 @@ WORKDIR /app ADD migration ./migration ADD assets/vsix ./assets/vsix +ADD assets/jetbrains ./assets/jetbrains COPY --from=builder /out/main /app/main diff --git a/backend/internal/user/handler/v1/user.go b/backend/internal/user/handler/v1/user.go index 2b356363..cc5efd8b 100644 --- a/backend/internal/user/handler/v1/user.go +++ b/backend/internal/user/handler/v1/user.go @@ -40,6 +40,7 @@ type UserHandler struct { logger *slog.Logger cfg *config.Config vsixCache map[string]*CacheEntry + zipCache map[string]*CacheEntry cacheMu sync.RWMutex limiter *rate.Limiter } @@ -68,11 +69,14 @@ func NewUserHandler( logger: logger, cfg: cfg, vsixCache: make(map[string]*CacheEntry), + zipCache: make(map[string]*CacheEntry), limiter: rate.NewLimiter(rate.Every(time.Duration(cfg.Extension.LimitSecond)*time.Second), cfg.Extension.Limit), } w.GET("/api/v1/static/vsix/:version", web.BaseHandler(u.VSIXDownload)) w.GET("/api/v1/static/vsix", web.BaseHandler(u.VSIXDownload)) + w.GET("/api/v1/static/jetbrains/:version", web.BaseHandler(u.ZipDownload)) + w.GET("/api/v1/static/jetbrains", web.BaseHandler(u.ZipDownload)) w.POST("/api/v1/vscode/init-auth", web.BindHandler(u.VSCodeAuthInit)) // admin @@ -170,6 +174,13 @@ func (h *UserHandler) cleanExpiredCache() { delete(h.vsixCache, key) } } + if h.zipCache != nil { + for key, entry := range h.zipCache { + if now.Sub(entry.createdAt) > time.Hour { + delete(h.zipCache, key) + } + } + } } // VSIXDownload 下载VSCode插件 @@ -236,6 +247,75 @@ func (h *UserHandler) VSIXDownload(c *web.Context) error { return err } +// ZipDownload 下载ZIP包(Jetbrains插件包) +// +// @Tags User +// @Summary 下载ZIP包 +// @Description 下载ZIP安装包(缓存1小时) +// @ID zip-download +// @Accept json +// @Produce octet-stream +// @Router /api/v1/static/jetbrains [get] +func (h *UserHandler) ZipDownload(c *web.Context) error { + if !h.limiter.Allow() { + return c.String(http.StatusTooManyRequests, "Too Many Requests") + } + + s, err := h.usecase.GetSetting(c.Request().Context()) + if err != nil { + return err + } + + host := c.Request().Host + h.logger.With("url", c.Request().URL).With("header", c.Request().Header).With("host", host).DebugContext(c.Request().Context(), "zip download") + cacheKey := h.generateCacheKey(version.Version, h.cfg.GetBaseURL(c.Request(), s)) + ver := strings.Trim(version.Version, "v") + + // 缓存命中 + h.cacheMu.RLock() + if entry, exists := h.zipCache[cacheKey]; exists { + if time.Since(entry.createdAt) < time.Hour { + h.cacheMu.RUnlock() + + disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.zip", ver) + c.Response().Header().Set("Content-Type", "application/octet-stream") + c.Response().Header().Set("Content-Disposition", disposition) + c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(entry.data))) + + fmt.Println(c.Response().Header()) + + _, werr := c.Response().Writer.Write(entry.data) + return werr + } + } + h.cacheMu.RUnlock() + + var buf bytes.Buffer + if err := vsix.ChangeVsixEndpoint(fmt.Sprintf("/app/assets/jetbrains/monkeycode-%s.zip", ver), "roo-code/package.json", h.cfg.GetBaseURL(c.Request(), s), &buf); err != nil { + return err + } + + data := buf.Bytes() + h.cacheMu.Lock() + h.zipCache[cacheKey] = &CacheEntry{ + data: data, + createdAt: time.Now(), + } + h.cacheMu.Unlock() + + // 异步清理 + go h.cleanExpiredCache() + + // 响应输出 + disposition := fmt.Sprintf("attachment; filename=monkeycode-%s.zip", ver) + c.Response().Header().Set("Content-Type", "application/octet-stream") + c.Response().Header().Set("Content-Disposition", disposition) + c.Response().Header().Set("Content-Length", fmt.Sprintf("%d", len(data))) + + _, err = c.Response().Writer.Write(data) + return err +} + // Login 用户登录 // // @Tags User