fix: 初验针对修改
This commit is contained in:
143
internal/health/readiness.go
Normal file
143
internal/health/readiness.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/ops/files/internal/config"
|
||||
"git.apinb.com/ops/files/internal/impl"
|
||||
"git.apinb.com/ops/files/internal/jobs"
|
||||
"git.apinb.com/ops/files/internal/runtimeinfo"
|
||||
"git.apinb.com/ops/files/internal/storage"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CheckResult struct {
|
||||
Name string `json:"name"`
|
||||
Required bool `json:"required"`
|
||||
Ready bool `json:"ready"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
type Readiness struct {
|
||||
Ready bool `json:"ready"`
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
Checks []CheckResult `json:"checks,omitempty"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type FileCounts struct {
|
||||
Total int64 `json:"total"`
|
||||
Ready int64 `json:"ready"`
|
||||
Pending int64 `json:"pending"`
|
||||
}
|
||||
|
||||
func Evaluate(ctx context.Context) Readiness {
|
||||
result, _ := evaluate(ctx)
|
||||
return result
|
||||
}
|
||||
|
||||
func evaluate(ctx context.Context) (Readiness, storage.RuntimeStatus) {
|
||||
result := Readiness{Ready: true, CheckedAt: time.Now().UTC(), Version: runtimeVersion()}
|
||||
result.add("config", true, config.Validate())
|
||||
result.add("database", true, pingDatabase(ctx, impl.DBService))
|
||||
storageStatus, storageErr := impl.StorageService.Status(ctx)
|
||||
if storageErr == nil && !storageStatus.Writable {
|
||||
storageErr = fmt.Errorf("文件存储当前不可写")
|
||||
}
|
||||
result.add("storage", true, storageErr)
|
||||
worker := jobs.Status()
|
||||
if worker.Running {
|
||||
result.add("pending_upload_cleanup", true, nil)
|
||||
} else {
|
||||
result.add("pending_upload_cleanup", true, fmt.Errorf("过期上传清理任务未运行"))
|
||||
}
|
||||
integrity := jobs.IntegrityStatusSnapshot()
|
||||
if integrity.Running {
|
||||
result.add("integrity_worker", true, nil)
|
||||
} else {
|
||||
result.add("integrity_worker", true, fmt.Errorf("文件完整性任务未运行"))
|
||||
}
|
||||
if integrity.Missing+integrity.SizeMismatch+integrity.ETagMismatch > 0 {
|
||||
result.add("file_integrity", false, fmt.Errorf("发现文件缺失或校验不一致"))
|
||||
} else {
|
||||
result.add("file_integrity", false, nil)
|
||||
}
|
||||
return result, storageStatus
|
||||
}
|
||||
|
||||
func runtimeVersion() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok || strings.TrimSpace(info.Main.Version) == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return info.Main.Version
|
||||
}
|
||||
func Ready(c *gin.Context) {
|
||||
result := Evaluate(c.Request.Context())
|
||||
status := http.StatusOK
|
||||
if !result.Ready {
|
||||
status = http.StatusServiceUnavailable
|
||||
}
|
||||
c.JSON(status, gin.H{"ready": result.Ready})
|
||||
}
|
||||
func Status(c *gin.Context) {
|
||||
result, storageStatus := evaluate(c.Request.Context())
|
||||
status := http.StatusOK
|
||||
if !result.Ready {
|
||||
status = http.StatusServiceUnavailable
|
||||
}
|
||||
counts, countErr := fileCounts(c.Request.Context())
|
||||
if countErr != nil {
|
||||
result.add("file_ledger", true, countErr)
|
||||
status = http.StatusServiceUnavailable
|
||||
}
|
||||
c.JSON(status, gin.H{
|
||||
"readiness": result,
|
||||
"workers": gin.H{
|
||||
"pending_upload_cleanup": jobs.Status(),
|
||||
"integrity": jobs.IntegrityStatusSnapshot(),
|
||||
},
|
||||
"storage": storageStatus,
|
||||
"disk": storageStatus.Disk,
|
||||
"files": counts,
|
||||
"activity": runtimeinfo.Snapshot(),
|
||||
})
|
||||
}
|
||||
|
||||
func fileCounts(ctx context.Context) (FileCounts, error) {
|
||||
var counts FileCounts
|
||||
err := impl.DBService.WithContext(ctx).Raw(`
|
||||
SELECT COUNT(*) AS total,
|
||||
COUNT(*) FILTER (WHERE status = 'ready') AS ready,
|
||||
COUNT(*) FILTER (WHERE status = 'pending') AS pending
|
||||
FROM files_object
|
||||
WHERE deleted_at IS NULL`).Scan(&counts).Error
|
||||
return counts, err
|
||||
}
|
||||
func (r *Readiness) add(name string, required bool, err error) {
|
||||
check := CheckResult{Name: name, Required: required, Ready: err == nil}
|
||||
if err != nil {
|
||||
check.Message = strings.TrimSpace(err.Error())
|
||||
if required {
|
||||
r.Ready = false
|
||||
}
|
||||
}
|
||||
r.Checks = append(r.Checks, check)
|
||||
}
|
||||
func pingDatabase(ctx context.Context, db *gorm.DB) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("数据库未初始化")
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checkCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
return sqlDB.PingContext(checkCtx)
|
||||
}
|
||||
Reference in New Issue
Block a user