更新若干功能
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
// Applies init.sql using the packaged sync defaults (build/sync.defaults.json).
|
||||
// Applies nl-pms-api/init.sql to MySQL.
|
||||
// Usage (from view repo root):
|
||||
//
|
||||
// go run ./tools/applyinit -dsn "user:pass@tcp(host:3306)/"
|
||||
//
|
||||
// Or set MYSQL_DSN. Schema source of truth is ../nl-pms-api/init.sql.
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -12,55 +17,39 @@ import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
type syncDefaults struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
}
|
||||
|
||||
func loadDefaults() syncDefaults {
|
||||
path := filepath.Join("build", "sync.defaults.json")
|
||||
func main() {
|
||||
dsnFlag := flag.String("dsn", "", "MySQL DSN without database (or set MYSQL_DSN)")
|
||||
sqlPath := flag.String("sql", "", "path to init.sql (default: ../nl-pms-api/init.sql)")
|
||||
flag.Parse()
|
||||
dsn := strings.TrimSpace(*dsnFlag)
|
||||
if dsn == "" {
|
||||
dsn = strings.TrimSpace(os.Getenv("MYSQL_DSN"))
|
||||
}
|
||||
if dsn == "" {
|
||||
fmt.Fprintln(os.Stderr, "缺少 -dsn 或 MYSQL_DSN(例: root:root@tcp(127.0.0.1:3306)/)")
|
||||
os.Exit(2)
|
||||
}
|
||||
if !strings.Contains(dsn, "multiStatements") {
|
||||
if strings.Contains(dsn, "?") {
|
||||
dsn += "&multiStatements=false&charset=utf8mb4"
|
||||
} else {
|
||||
dsn += "?multiStatements=false&charset=utf8mb4"
|
||||
}
|
||||
}
|
||||
path := *sqlPath
|
||||
if path == "" {
|
||||
path = filepath.Join("..", "nl-pms-api", "init.sql")
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var c syncDefaults
|
||||
if err := json.Unmarshal(raw, &c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if c.Port <= 0 {
|
||||
c.Port = 3306
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func dsn(c syncDefaults, withDB bool) string {
|
||||
db := ""
|
||||
if withDB {
|
||||
db = c.Database
|
||||
}
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?multiStatements=false&charset=utf8mb4",
|
||||
c.User, c.Password, c.Host, c.Port, db)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := loadDefaults()
|
||||
if len(os.Args) > 1 && os.Args[1] == "inspect" {
|
||||
inspect(cfg)
|
||||
return
|
||||
}
|
||||
raw, err := os.ReadFile("init.sql")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db, err := sql.Open("mysql", dsn(cfg, false))
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer db.Close()
|
||||
// init.sql 的升级段依赖会话变量(SET @sql / PREPARE),必须固定在同一连接上执行。
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
var kept []string
|
||||
@@ -81,11 +70,11 @@ func main() {
|
||||
}
|
||||
}
|
||||
var n int
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM " + cfg.Database + ".sync_settings").Scan(&n); err != nil {
|
||||
if err := db.QueryRow("SELECT COUNT(*) FROM code_count.users").Scan(&n); err != nil {
|
||||
fmt.Println("verify failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("ok, sync_settings rows:", n, "host:", cfg.Host)
|
||||
fmt.Println("ok, users rows:", n, "sql:", path)
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
@@ -94,25 +83,3 @@ func min(a, b int) int {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func inspect(cfg syncDefaults) {
|
||||
db, err := sql.Open("mysql", dsn(cfg, true))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer db.Close()
|
||||
rows, err := db.Query("SELECT user_id, name, LENGTH(value), updated_at FROM sync_settings ORDER BY user_id, name")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var uid int64
|
||||
var name, at string
|
||||
var n int
|
||||
if err := rows.Scan(&uid, &name, &n, &at); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("user=%d name=%-12s bytes=%-6d at=%s\n", uid, name, n, at)
|
||||
}
|
||||
}
|
||||
|
||||
86
tools/package-api.ps1
Normal file
86
tools/package-api.ps1
Normal file
@@ -0,0 +1,86 @@
|
||||
# Package nl-pms-api for desktop "package" task.
|
||||
# Rebuilds only when sources change; pass -Force to always rebuild.
|
||||
# Output: view/bin/nl-pms-api/ (linux/amd64 binary + init.sql + migrations + config.example.yaml)
|
||||
|
||||
param(
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ViewRoot = Split-Path -Parent $PSScriptRoot
|
||||
$ApiRoot = Join-Path (Split-Path -Parent $ViewRoot) 'nl-pms-api'
|
||||
$OutDir = Join-Path $ViewRoot 'bin\nl-pms-api'
|
||||
$StampFile = Join-Path $OutDir '.stamp'
|
||||
$BinPath = Join-Path $OutDir 'nl-pms-api'
|
||||
|
||||
if (-not (Test-Path $ApiRoot)) {
|
||||
Write-Error "API dir not found: $ApiRoot (expected sibling of view)"
|
||||
}
|
||||
|
||||
function Get-ApiFingerprint {
|
||||
$files = @()
|
||||
$files += Get-ChildItem -Path $ApiRoot -Filter 'go.mod' -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path $ApiRoot -Filter 'go.sum' -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path $ApiRoot -Filter 'main.go' -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path $ApiRoot -Filter 'init.sql' -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path $ApiRoot -Filter 'config.example.yaml' -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path (Join-Path $ApiRoot 'internal') -Recurse -Include *.go -File -ErrorAction SilentlyContinue
|
||||
$files += Get-ChildItem -Path (Join-Path $ApiRoot 'migrations') -Recurse -Include *.sql -File -ErrorAction SilentlyContinue
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
$ms = New-Object System.IO.MemoryStream
|
||||
foreach ($f in ($files | Sort-Object FullName)) {
|
||||
$rel = $f.FullName.Substring($ApiRoot.Length).TrimStart('\', '/')
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($rel + "`n")
|
||||
$ms.Write($bytes, 0, $bytes.Length)
|
||||
$content = [System.IO.File]::ReadAllBytes($f.FullName)
|
||||
$ms.Write($content, 0, $content.Length)
|
||||
}
|
||||
$hash = $sha.ComputeHash($ms.ToArray())
|
||||
($hash | ForEach-Object { $_.ToString('x2') }) -join ''
|
||||
}
|
||||
|
||||
$fp = Get-ApiFingerprint
|
||||
$prev = ''
|
||||
if (Test-Path $StampFile) { $prev = (Get-Content -Raw $StampFile).Trim() }
|
||||
|
||||
if (-not $Force -and $prev -eq $fp -and (Test-Path $BinPath)) {
|
||||
Write-Host "api: up to date ($fp)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "api: packaging -> $OutDir"
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
$migOut = Join-Path $OutDir 'migrations'
|
||||
New-Item -ItemType Directory -Force -Path $migOut | Out-Null
|
||||
|
||||
$prevGoos = $env:GOOS
|
||||
$prevGoarch = $env:GOARCH
|
||||
$prevCgo = $env:CGO_ENABLED
|
||||
$env:CGO_ENABLED = '0'
|
||||
$env:GOOS = 'linux'
|
||||
$env:GOARCH = 'amd64'
|
||||
Push-Location $ApiRoot
|
||||
try {
|
||||
& go build -trimpath -ldflags '-s -w' -o $BinPath .
|
||||
if ($LASTEXITCODE -ne 0) { throw "go build failed: $LASTEXITCODE" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
if ($null -eq $prevGoos) { Remove-Item Env:GOOS -ErrorAction SilentlyContinue } else { $env:GOOS = $prevGoos }
|
||||
if ($null -eq $prevGoarch) { Remove-Item Env:GOARCH -ErrorAction SilentlyContinue } else { $env:GOARCH = $prevGoarch }
|
||||
if ($null -eq $prevCgo) { Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue } else { $env:CGO_ENABLED = $prevCgo }
|
||||
}
|
||||
|
||||
Copy-Item -Force (Join-Path $ApiRoot 'init.sql') (Join-Path $OutDir 'init.sql')
|
||||
Copy-Item -Force (Join-Path $ApiRoot 'config.example.yaml') (Join-Path $OutDir 'config.example.yaml')
|
||||
Get-ChildItem -Path (Join-Path $ApiRoot 'migrations') -Filter '*.sql' -File -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Copy-Item -Force $_.FullName (Join-Path $migOut $_.Name) }
|
||||
|
||||
# Must be linux ELF (not Windows PE). Magic: 7F 45 4C 46
|
||||
$hdr = Get-Content -Path $BinPath -Encoding Byte -TotalCount 4
|
||||
if ($hdr.Count -lt 4 -or $hdr[0] -ne 0x7F -or $hdr[1] -ne 0x45 -or $hdr[2] -ne 0x4C -or $hdr[3] -ne 0x46) {
|
||||
Remove-Item -Force $BinPath -ErrorAction SilentlyContinue
|
||||
throw "api binary is not linux ELF (GOOS=linux GOARCH=amd64 required). Got header: $($hdr -join ',')"
|
||||
}
|
||||
|
||||
Set-Content -Path $StampFile -Value $fp -NoNewline
|
||||
Write-Host "api: done linux/amd64 ELF -> $BinPath"
|
||||
Reference in New Issue
Block a user