A:增加govips的调用

This commit is contained in:
jimmy committed 2025-11-16 17:33:39 +08:00
1 parent 3c17a7fff6
commit e5009ee984
5 files changed
+233 -3

No files matched your search

+5 -1
View File
@@ -2,7 +2,11 @@
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.claude" />
<excludeFolder url="file://$MODULE_DIR$/.github" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
+42
View File
@@ -0,0 +1,42 @@
# Repository Guidelines
## Project Structure & Module Organization
- Root Go service is in `./` with `main.go`, `go.mod`, and `config.yaml`.
- Core backend lives in `internal/` (API, handlers, services, repositories, workflows, utilities).
- Reusable libraries are in `pkg/` (logging, SSE, system utilities, image helpers).
- HTTP request examples live in `tests/` (`*.http`) and complement Go tests.
- Supporting assets and tools: `tools/` (external binaries), `scripts/` (build), `db/`, `logs/`, `examples/`, `doc/`.
## Build, Test, and Development Commands
- Install deps: `go mod download`.
- Run in dev: `go run main.go` (uses `config.yaml`).
- Build local binary: `go build -o rear.exe .`.
- Cross-platform build: `bash scripts/build.sh` (from `rear/`, requires Bash and Go).
- Run tests: `go test ./...` (unit/integration), plus manual API checks via `.http` files in `tests/`.
## Coding Style & Naming Conventions
- Language: Go 1.24, format all code with `gofmt`/editor-on-save (`go fmt ./...` before committing).
- Package names: short, lowercase (`filesystem`, `logger`, `sse`); files use `snake_case.go`.
- Types and exported functions: `PascalCase`; locals and unexported symbols: `camelCase`.
- Keep handlers thin, push business logic into `internal/service` and persistence into `internal/repositories`.
## Testing Guidelines
- Add/maintain Go tests under the same package with `*_test.go` suffix (`go test ./pkg/...` for focused runs).
- Mirror new behavior with table-driven tests where practical.
- Prefer deterministic tests; avoid relying on real external tools or network when possible.
- Use `.http` files in `tests/` to document and validate API flows when endpoints change.
## Commit & Pull Request Guidelines
- Follow conventional commits: `feat(scope): summary`, `fix(scope): summary`, `chore(scope): summary`, `docs(scope): summary`, etc. Scopes like `sse`, `filesystem`, `repo`, `tests`.
- Use present-tense, concise messages; keep one logical change per commit.
- PRs should include: brief description, motivation/context, checklist of changes, how to test (commands and sample requests), and screenshots or log snippets when relevant.
## Security & Configuration Tips
- Never commit real credentials or private file paths; use `config.yaml.example` as the template and keep local `config.yaml` environment-specific.
- Treat `tools/` binaries as build/runtime dependencies; do not modify or repackage them without confirming licenses and platform impact.
- When introducing new external tools or ports, document them in `README.md` and `doc/` and ensure they are configurable via `config.yaml`.
## Agent-Specific Instructions
- Keep changes minimal and scoped; prefer extending existing modules under `internal/` or `pkg/` instead of creating new top-level directories.
- Do not introduce new Go dependencies unless necessary; if added, group them logically in `go.mod` and explain the rationale in the PR description.
- When modifying behavior, update or add tests in the nearest relevant package and, if API-visible, adjust `.http` examples in `tests/`.
+3 -2
View File
@@ -3,8 +3,6 @@ package api
import (
"context"
"fmt"
"github.com/h2non/filetype"
"go.uber.org/zap"
"os"
"path/filepath"
"rear/internal/config"
@@ -15,6 +13,9 @@ import (
"rear/pkg/utils"
"strconv"
"strings"
"github.com/h2non/filetype"
"go.uber.org/zap"
)
type ImageAPI struct {
@@ -0,0 +1,180 @@
//go:build linux || darwin
package tools
import (
"context"
"fmt"
"os"
"rear/internal/consts"
"sync"
vips "github.com/davidbyttow/govips/v2/vips"
)
var (
goVipsInitOnce sync.Once
goVipsInitErr error
)
// ensureGoVipsInitialized 确保 govips 已初始化(仅在 Linux/macOS 下编译)
func ensureGoVipsInitialized() error {
goVipsInitOnce.Do(func() {
// 使用默认配置启动 govips,具体配置可在以后按需扩展
goVipsInitErr = vips.Startup(nil)
})
return goVipsInitErr
}
// IsGoVipsAvailable 检查 govips 是否可用(是否能够成功初始化)
func IsGoVipsAvailable() bool {
return ensureGoVipsInitialized() == nil
}
// ConvertVipsImageWithGoVips 使用 govips 进行格式转换
// 签名与 ConvertVipsImage 基本一致,方便在调用侧切换实现。
func ConvertVipsImageWithGoVips(ctx context.Context, inputPath, outputPath string, format consts.ImageFormat, options *VipsConvertOptions) error {
if err := ensureGoVipsInitialized(); err != nil {
return fmt.Errorf("govips 初始化失败: %w", err)
}
if options == nil {
options = &VipsConvertOptions{Quality: 85}
}
image, err := vips.NewImageFromFile(inputPath)
if err != nil {
return fmt.Errorf("govips 加载图片失败: %w", err)
}
defer image.Close()
var (
data []byte
)
switch format {
case consts.FormatJPG:
params := vips.JpegExportParams{
Quality: options.Quality,
StripMetadata: options.StripMeta,
Interlace: options.Progressive,
}
data, _, err = image.ExportJpeg(&params)
case consts.FormatPNG:
compression := 0
if options.Quality > 0 {
// 与 CLI 方式保持一致,将 1-100 映射到 0-9 压缩级别
compression = 9 - (options.Quality-1)*9/99
}
params := vips.PngExportParams{
Compression: compression,
StripMetadata: options.StripMeta,
}
data, _, err = image.ExportPng(&params)
case consts.FormatWEBP:
params := vips.WebpExportParams{
Quality: options.Quality,
Lossless: options.Lossless,
StripMetadata: options.StripMeta,
}
data, _, err = image.ExportWebp(&params)
default:
return fmt.Errorf("unsupported format for govips: %s", format)
}
if err != nil {
return fmt.Errorf("govips 导出图片失败: %w", err)
}
if err := os.WriteFile(outputPath, data, 0o644); err != nil {
return fmt.Errorf("写出图片失败: %w", err)
}
return nil
}
// CompressVipsImageWithGoVips 使用 govips 进行综合压缩(缩放 + 去元数据 + 质量控制 + 格式转换)
// 行为尽可能与 CompressVipsImage 保持一致,但采用 libvips C 接口而非 CLI。
func CompressVipsImageWithGoVips(ctx context.Context, inputPath, outputPath string,
targetFormat consts.ImageFormat, targetWidth, targetHeight, quality int) error {
if err := ensureGoVipsInitialized(); err != nil {
return fmt.Errorf("govips 初始化失败: %w", err)
}
if targetWidth <= 0 || targetHeight <= 0 {
return fmt.Errorf("invalid target dimensions: width and height must be positive")
}
if quality < 1 || quality > 100 {
return fmt.Errorf("invalid quality: must be between 1 and 100")
}
image, err := vips.NewImageFromFile(inputPath)
if err != nil {
return fmt.Errorf("govips 加载图片失败: %w", err)
}
defer image.Close()
// 计算缩放比例(保持宽高比,且不放大)
width := image.Width()
height := image.Height()
if width <= 0 || height <= 0 {
return fmt.Errorf("invalid source dimensions: %dx%d", width, height)
}
scaleX := float64(targetWidth) / float64(width)
scaleY := float64(targetHeight) / float64(height)
scale := scaleX
if scaleY < scaleX {
scale = scaleY
}
if scale <= 0 {
return fmt.Errorf("invalid resize scale computed: %f", scale)
}
if scale > 1.0 {
scale = 1.0
}
if err := image.Resize(scale, vips.KernelLanczos3); err != nil {
return fmt.Errorf("govips 缩放图片失败: %w", err)
}
// 导出为目标格式
var data []byte
switch targetFormat {
case consts.FormatJPG:
params := vips.JpegExportParams{
Quality: quality,
StripMetadata: true,
Interlace: false,
}
data, _, err = image.ExportJpeg(&params)
case consts.FormatPNG:
compression := 9 - (quality-1)*9/99
params := vips.PngExportParams{
Compression: compression,
StripMetadata: true,
}
data, _, err = image.ExportPng(&params)
case consts.FormatWEBP:
params := vips.WebpExportParams{
Quality: quality,
Lossless: false,
StripMetadata: true,
}
data, _, err = image.ExportWebp(&params)
default:
return fmt.Errorf("unsupported target format for govips: %s", targetFormat)
}
if err != nil {
return fmt.Errorf("govips 导出压缩图片失败: %w", err)
}
if err := os.WriteFile(outputPath, data, 0o644); err != nil {
return fmt.Errorf("写出压缩图片失败: %w", err)
}
return nil
}
+3
View File
@@ -154,3 +154,6 @@
{"level":"info","time":"2025-10-06 17:28:15.373","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}
{"level":"info","time":"2025-10-06 17:28:49.241","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}
{"level":"info","time":"2025-10-06 17:38:05.412","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}
{"level":"info","time":"2025-11-16 14:43:16.424","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}
{"level":"info","time":"2025-11-16 17:26:19.223","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}
{"level":"info","time":"2025-11-16 17:32:50.846","caller":"config/config.go:90","msg":"加载配置文件成功","path":"config.yaml"}