feat: add cache, outbound HTTP, request protection, tasks and module scaffolding

This commit is contained in:
2026-09-23 13:38:25 +08:00
parent 467c890363
commit fe66221a9c
34 changed files with 3100 additions and 1 deletions

133
examples/modules/main.go Normal file
View File

@@ -0,0 +1,133 @@
// modules combines caching, outbound calls, middleware and background jobs.
package main
import (
"context"
"encoding/json"
"errors"
"log"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
omni "git.yuntuo.hk/k2safe/go-omni"
"git.yuntuo.hk/k2safe/go-omni/auth"
"git.yuntuo.hk/k2safe/go-omni/cache"
"git.yuntuo.hk/k2safe/go-omni/httpc"
"git.yuntuo.hk/k2safe/go-omni/logx"
"git.yuntuo.hk/k2safe/go-omni/middleware"
"git.yuntuo.hk/k2safe/go-omni/task"
)
func newApp(settings omni.Config, token, upstream string) (application *omni.App, err error) {
application = omni.New(omni.WithConfig(settings))
defer func(app *omni.App) {
if err != nil {
_ = app.Close()
}
}(application)
store, err := cache.NewMemory(cache.MemoryConfig{MaxEntries: 128, MaxBytes: 1 << 20})
if err != nil {
return nil, err
}
application.OnShutdown(store.Close)
loader, err := cache.NewLoader(store, cache.LoaderConfig{MaxLoads: 8, Timeout: time.Second})
if err != nil {
return application, err
}
client, err := httpc.New(httpc.Config{Timeout: time.Second, MaxRetries: 1, MaxConnsPerHost: 8})
if err != nil {
return application, err
}
application.OnShutdown(client.Close)
jobs, err := task.New(task.Config{Workers: 2, QueueSize: 16, TaskTimeout: 2 * time.Second})
if err != nil {
return application, err
}
// Reverse cleanup order: drain jobs before closing their dependencies.
application.OnShutdown(jobs.Close)
if err = jobs.Every("reportStats", time.Minute, func(ctx context.Context) error {
logx.InfoContext(ctx, "jobs.stats", "stats", jobs.Stats())
return nil
}); err != nil {
return application, err
}
limiter, err := middleware.NewRateLimiter(middleware.RateLimitConfig{Rate: 20, Burst: 40, MaxKeys: 128, Key: func(c *omni.Context) string { p, _ := auth.FromContext(c); return p.UserID }})
if err != nil {
return application, err
}
idempotency, err := middleware.NewIdempotency(middleware.IdempotencyConfig{Scope: func(c *omni.Context) (string, error) { p, _ := auth.FromContext(c); return p.UserID, nil }})
if err != nil {
return application, err
}
api := application.Group("/api", middleware.Timeout(2*time.Second), auth.RequireAuth(auth.StaticAuthenticator{token: {UserID: "demo-user"}}), limiter.Middleware())
api.GET("/catalog", func(c *omni.Context) error {
value, err := loader.GetOrLoad(c.Context(), "publicCatalog:v1", 30*time.Second, func(ctx context.Context) ([]byte, error) {
data := map[string]any{"name": "demo catalog"}
if upstream != "" {
if err := client.JSON(ctx, "GET", upstream, nil, &data); err != nil {
return nil, err
}
}
return json.Marshal(data)
})
if err != nil {
return err
}
return c.OK(json.RawMessage(value))
})
var nextID atomic.Uint64
api.POST("/jobs", func(c *omni.Context) error {
var input struct {
Name string `json:"name" binding:"required,max=100"`
}
if err := c.BindJSON(&input); err != nil {
return err
}
id := nextID.Add(1)
// This job deliberately outlives the HTTP request. Runner still applies
// its own deadline and shutdown cancellation; log correlation survives.
_, err := jobs.Submit(context.WithoutCancel(c.Context()), "demoJob", func(ctx context.Context) error {
logx.InfoContext(ctx, "job.finished", "jobId", id, "name", input.Name)
return nil
})
if errors.Is(err, task.ErrQueueFull) {
return omni.NewError(503, "QUEUE_FULL", "job queue is full")
}
if err != nil {
return err
}
return c.Accepted(map[string]uint64{"jobId": id})
}, idempotency.Middleware())
api.GET("/jobs", func(c *omni.Context) error { return c.OK(jobs.Stats()) })
return application, nil
}
func run(ctx context.Context) error {
settings, err := omni.LoadConfig("")
if err != nil {
return err
}
if os.Getenv("GO_OMNI_HTTP_ADDR") == "" {
settings.Address = "127.0.0.1:8080"
}
token := os.Getenv("GO_OMNI_DEV_TOKEN")
if token == "" {
token = "dev-token"
}
application, err := newApp(settings, token, os.Getenv("GO_OMNI_DEMO_UPSTREAM"))
if err != nil {
return err
}
return application.RunContext(ctx)
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := run(ctx); err != nil {
log.Fatal(err)
}
}