diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 5c3fc41..0000000 --- a/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -.git -.github -.trae -deploy -**/*_test.go -**/*.log -**/*.tmp -**/*.swp -**/.DS_Store -**/node_modules -**/.vscode diff --git a/.gitignore b/.gitignore index 660b06e..07c2036 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .trae .vscode -__debug* \ No newline at end of file +__debug* +dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9e16b9c..0000000 --- a/Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -FROM golang:1.21-alpine AS builder - -ARG APK_MIRROR=https://mirrors.aliyun.com/alpine -ARG GOPROXY=https://goproxy.cn,direct - -RUN set -eux; \ - printf '%s\n' \ - "${APK_MIRROR}/v3.20/main" \ - "${APK_MIRROR}/v3.20/community" > /etc/apk/repositories; \ - apk add --no-cache ca-certificates tzdata git - -WORKDIR /src - -COPY go.mod go.sum ./ -ENV GOPROXY=${GOPROXY} - -RUN go mod download - -COPY . . - -RUN set -eux; \ - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -trimpath -ldflags="-s -w" -o /out/qr-scanner . - -FROM alpine:3.20 - -ARG APK_MIRROR=https://mirrors.aliyun.com/alpine - -RUN set -eux; \ - printf '%s\n' \ - "${APK_MIRROR}/v3.20/main" \ - "${APK_MIRROR}/v3.20/community" > /etc/apk/repositories; \ - apk add --no-cache ca-certificates tzdata; \ - addgroup -S app && adduser -S -G app app - -WORKDIR /app - -COPY --from=builder /out/qr-scanner /app/qr-scanner -COPY --from=builder /src/static /app/static - -USER app -EXPOSE 8080 -ENTRYPOINT ["/app/qr-scanner"] diff --git a/README.md b/README.md index adb992b..9e30667 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 环境变量: -- `QR_SCANNER_PORT`:端口(默认 8080) +- `QR_SCANNER_PORT`:端口(默认 8001) - `QR_SCANNER_TEMP_DIR`:临时目录(默认 /tmp/qr-scanner) - `QR_SCANNER_RETENTION_MINUTES`:结果保留分钟数(默认 30) - `QR_SCANNER_DEFAULT_WORKERS`:默认并发(默认 4) @@ -29,7 +29,7 @@ go run . 浏览器打开: -- `http://localhost:8080/` +- `http://localhost:8001/` ## API(v1.0) diff --git a/config/config.go b/config/config.go index c23bd06..2a30b11 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,7 @@ type Config struct { func Default() Config { return Config{ Host: "0.0.0.0", - Port: 8080, + Port: 8001, TempDir: "/tmp/qr-scanner", MaxUploadMB: 100, MaxFiles: 10000, diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..64b532e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT_DIR" + +OUT_DIR="$ROOT_DIR/dist" +mkdir -p "$OUT_DIR" + +APP_NAME="qr-scanner" +OUT_EXE="$OUT_DIR/${APP_NAME}.exe" + +echo "Building Windows exe -> $OUT_EXE" + +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ + go build -trimpath -ldflags "-s -w" -o "$OUT_EXE" . + +echo "Done." +echo "Run on Windows: double-click ${APP_NAME}.exe (auto opens http://localhost:8001/)" diff --git a/main.go b/main.go index d827d6e..a1ef200 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,14 @@ package main import ( + "embed" "fmt" + "io/fs" + "net" + "net/http" "os" + "os/exec" + "runtime" "time" "github.com/gin-gonic/gin" @@ -12,6 +18,9 @@ import ( "qr-scanner/services" ) +//go:embed static/* +var staticFS embed.FS + func main() { cfg := config.Default().WithEnv() @@ -35,8 +44,12 @@ func main() { r.MaxMultipartMemory = cfg.MaxUploadMB * 1024 * 1024 } - r.GET("/", func(c *gin.Context) { c.File("./static/index.html") }) - r.Static("/static", "./static") + sub, err := fs.Sub(staticFS, "static") + if err != nil { + panic(err) + } + r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusFound, "/static/index.html") }) + r.StaticFS("/static", http.FS(sub)) api := r.Group("/api") { @@ -59,7 +72,34 @@ func main() { }() addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) - if err := r.Run(addr); err != nil { + ln, err := net.Listen("tcp", addr) + if err != nil { + panic(err) + } + + openURL := fmt.Sprintf("http://%s:%d/", browserHost(cfg.Host), cfg.Port) + _ = openBrowser(openURL) + + srv := &http.Server{Handler: r} + if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { panic(err) } } + +func browserHost(listenHost string) string { + if listenHost == "" || listenHost == "0.0.0.0" || listenHost == "::" { + return "localhost" + } + return listenHost +} + +func openBrowser(url string) error { + switch runtime.GOOS { + case "windows": + return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + return exec.Command("open", url).Start() + default: + return exec.Command("xdg-open", url).Start() + } +}