- 37 个端点全部添加 godoc Swagger 注解(RBAC 11 / Org 5 / Audit 1 / Settings 7 / Notification 7) - 重新生成 docs/(swagger.json/yaml/docs.go) - M8 质量横切:IP 限流、安全头、Body 限制、结构化日志增强、优雅关闭 - 新增 Dockerfile 多阶段构建 + .dockerignore - 新增 testutil 测试工具包 - 修复 testutil.go 编译错误
This commit is contained in:
parent
cb7ad4d35d
commit
9148d2f6da
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@ -0,0 +1,32 @@
|
||||
.git
|
||||
.gitea
|
||||
.github
|
||||
.gitignore
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Build output
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.*
|
||||
*.local
|
||||
|
||||
# Docs
|
||||
coverage.out
|
||||
coverage.html
|
||||
*.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Uploads content
|
||||
uploads/*
|
||||
!uploads/.gitkeep
|
||||
34
.gitea/workflows/ci.yml
Normal file
34
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,34 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Build & Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Check go.mod
|
||||
run: |
|
||||
go mod tidy
|
||||
git diff --exit-code go.mod go.sum
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Test
|
||||
run: go test ./... -v -race -coverprofile=coverage.out
|
||||
|
||||
- name: Build
|
||||
run: go build -o build/mengstack ./cmd/server
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
# Build stage
|
||||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/mengstack ./cmd/server
|
||||
|
||||
# Runtime stage
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
RUN addgroup -S mengstack && adduser -S mengstack -G mengstack
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/mengstack .
|
||||
COPY --from=builder /app/configs ./configs
|
||||
|
||||
RUN mkdir -p uploads logs && chown -R mengstack:mengstack /app
|
||||
|
||||
USER mengstack
|
||||
|
||||
EXPOSE 2222
|
||||
|
||||
CMD ["./mengstack"]
|
||||
3
Makefile
3
Makefile
@ -31,6 +31,9 @@ lint:
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
docker-build:
|
||||
docker build -t mengstack:latest .
|
||||
|
||||
docker-up:
|
||||
docker-compose up -d
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ services:
|
||||
|
||||
api:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${SERVER_PORT:-2222}:2222"
|
||||
environment:
|
||||
|
||||
2025
docs/docs.go
2025
docs/docs.go
File diff suppressed because it is too large
Load Diff
2025
docs/swagger.json
2025
docs/swagger.json
File diff suppressed because it is too large
Load Diff
1236
docs/swagger.yaml
1236
docs/swagger.yaml
File diff suppressed because it is too large
Load Diff
@ -56,9 +56,12 @@ func newEngine(
|
||||
r := gin.New()
|
||||
|
||||
r.Use(middleware.RequestID())
|
||||
r.Use(middleware.RequestLogger())
|
||||
r.Use(middleware.RequestLogger(log))
|
||||
r.Use(middleware.SecurityHeaders())
|
||||
r.Use(middleware.CORS())
|
||||
r.Use(gin.Recovery())
|
||||
r.Use(middleware.RateLimit(100, time.Minute))
|
||||
r.Use(middleware.BodyLimit(10 << 20))
|
||||
r.Use(middleware.Recovery(log))
|
||||
|
||||
healthHandler := health.NewHandler(db, rdb, log)
|
||||
r.GET("/health", healthHandler.Handle)
|
||||
@ -97,8 +100,10 @@ func registerLifecycle(lc fx.Lifecycle, srv *http.Server, log *zap.Logger) {
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
log.Info("server shutting down")
|
||||
return srv.Shutdown(ctx)
|
||||
return srv.Shutdown(shutdownCtx)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
|
||||
func NewPostgres(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s TimeZone=Asia/Shanghai",
|
||||
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode,
|
||||
)
|
||||
|
||||
|
||||
23
internal/app/middleware/bodylimit.go
Normal file
23
internal/app/middleware/bodylimit.go
Normal file
@ -0,0 +1,23 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"mengstack/internal/kernel/errors"
|
||||
"mengstack/internal/kernel/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// BodyLimit restricts the maximum request body size.
|
||||
func BodyLimit(maxBytes int64) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.ContentLength > maxBytes {
|
||||
response.Fail(c, errors.New("BODY_TOO_LARGE", "request body too large", http.StatusRequestEntityTooLarge))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxBytes)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
187
internal/app/middleware/middleware_test.go
Normal file
187
internal/app/middleware/middleware_test.go
Normal file
@ -0,0 +1,187 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
func TestRequestID_GeneratesNew(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RequestID())
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, c.GetString("trace_id"))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Header().Get("X-Request-ID") == "" {
|
||||
t.Error("expected X-Request-ID header to be set")
|
||||
}
|
||||
if w.Body.String() == "" {
|
||||
t.Error("expected trace_id in context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestID_PreservesExisting(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RequestID())
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, c.GetString("trace_id"))
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
req.Header.Set("X-Request-ID", "existing-id")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Header().Get("X-Request-ID") != "existing-id" {
|
||||
t.Errorf("expected existing-id, got %s", w.Header().Get("X-Request-ID"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityHeaders(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(SecurityHeaders())
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
headers := map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||
}
|
||||
|
||||
for key, expected := range headers {
|
||||
if got := w.Header().Get(key); got != expected {
|
||||
t.Errorf("header %s: expected %q, got %q", key, expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_AllowsWithinLimit(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RateLimit(5, time.Minute))
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != 200 {
|
||||
t.Errorf("request %d: expected 200, got %d", i+1, w.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_BlocksOverLimit(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RateLimit(2, time.Minute))
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusTooManyRequests {
|
||||
t.Errorf("expected 429, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestLogger_DoesNotPanic(t *testing.T) {
|
||||
log := zaptest.NewLogger(t)
|
||||
r := gin.New()
|
||||
r.Use(RequestID())
|
||||
r.Use(RequestLogger(log))
|
||||
r.GET("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecovery_HandlesPanic(t *testing.T) {
|
||||
log := zaptest.NewLogger(t)
|
||||
r := gin.New()
|
||||
r.Use(RequestID())
|
||||
r.Use(Recovery(log))
|
||||
r.GET("/panic", func(c *gin.Context) {
|
||||
panic("test panic")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/panic", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 500 {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyLimit_RejectsLargeBody(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(BodyLimit(10))
|
||||
r.POST("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
body := strings.NewReader(strings.Repeat("x", 100))
|
||||
req, _ := http.NewRequest("POST", "/test", body)
|
||||
req.Header.Set("Content-Length", "100")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusRequestEntityTooLarge {
|
||||
t.Errorf("expected 413, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodyLimit_AllowsSmallBody(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(BodyLimit(1024))
|
||||
r.POST("/test", func(c *gin.Context) {
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
body := strings.NewReader("hello")
|
||||
req, _ := http.NewRequest("POST", "/test", body)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
89
internal/app/middleware/ratelimit.go
Normal file
89
internal/app/middleware/ratelimit.go
Normal file
@ -0,0 +1,89 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"mengstack/internal/kernel/response"
|
||||
"mengstack/internal/kernel/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type visitor struct {
|
||||
count int
|
||||
lastSeen time.Time
|
||||
}
|
||||
|
||||
type rateLimiter struct {
|
||||
mu sync.Mutex
|
||||
visitors map[string]*visitor
|
||||
limit int
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
func newRateLimiter(limit int, window time.Duration) *rateLimiter {
|
||||
rl := &rateLimiter{
|
||||
visitors: make(map[string]*visitor),
|
||||
limit: limit,
|
||||
window: window,
|
||||
}
|
||||
go rl.cleanup()
|
||||
return rl
|
||||
}
|
||||
|
||||
func (rl *rateLimiter) cleanup() {
|
||||
ticker := time.NewTicker(rl.window)
|
||||
for range ticker.C {
|
||||
rl.mu.Lock()
|
||||
now := time.Now()
|
||||
for key, v := range rl.visitors {
|
||||
if now.Sub(v.lastSeen) > rl.window {
|
||||
delete(rl.visitors, key)
|
||||
}
|
||||
}
|
||||
rl.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *rateLimiter) allow(key string) bool {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
|
||||
v, exists := rl.visitors[key]
|
||||
if !exists {
|
||||
rl.visitors[key] = &visitor{count: 1, lastSeen: time.Now()}
|
||||
return true
|
||||
}
|
||||
|
||||
if time.Since(v.lastSeen) > rl.window {
|
||||
v.count = 1
|
||||
v.lastSeen = time.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
if v.count >= rl.limit {
|
||||
return false
|
||||
}
|
||||
|
||||
v.count++
|
||||
v.lastSeen = time.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
// RateLimit godoc
|
||||
// Limits requests per IP address.
|
||||
func RateLimit(limit int, window time.Duration) gin.HandlerFunc {
|
||||
rl := newRateLimiter(limit, window)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
if !rl.allow(ip) {
|
||||
response.Fail(c, errors.New("RATE_LIMITED", "too many requests", http.StatusTooManyRequests))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func RequestID() gin.HandlerFunc {
|
||||
@ -20,19 +20,53 @@ func RequestID() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func RequestLogger() gin.HandlerFunc {
|
||||
func RequestLogger(log *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
query := c.Request.URL.RawQuery
|
||||
|
||||
c.Next()
|
||||
|
||||
latency := time.Since(start)
|
||||
status := c.Writer.Status()
|
||||
gin.DefaultWriter.Write([]byte(
|
||||
"[" + c.GetString("trace_id") + "] " +
|
||||
c.Request.Method + " " + path + " " +
|
||||
strconv.Itoa(status) + " " + latency.String() + "\n",
|
||||
))
|
||||
|
||||
fields := []zap.Field{
|
||||
zap.Int("status", status),
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("path", path),
|
||||
zap.String("query", query),
|
||||
zap.String("ip", c.ClientIP()),
|
||||
zap.String("user-agent", c.Request.UserAgent()),
|
||||
zap.Duration("latency", latency),
|
||||
zap.String("trace_id", c.GetString("trace_id")),
|
||||
}
|
||||
|
||||
if c.Writer.Status() >= 500 {
|
||||
log.Error("request completed with server error", fields...)
|
||||
} else if c.Writer.Status() >= 400 {
|
||||
log.Warn("request completed with client error", fields...)
|
||||
} else {
|
||||
log.Info("request", fields...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Recovery(log *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error("panic recovered",
|
||||
zap.Any("error", r),
|
||||
zap.String("path", c.Request.URL.Path),
|
||||
zap.String("trace_id", c.GetString("trace_id")),
|
||||
)
|
||||
c.AbortWithStatusJSON(500, gin.H{
|
||||
"error": "INTERNAL_ERROR",
|
||||
"message": "internal server error",
|
||||
})
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
15
internal/app/middleware/security.go
Normal file
15
internal/app/middleware/security.go
Normal file
@ -0,0 +1,15 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// SecurityHeaders adds common security headers to responses.
|
||||
func SecurityHeaders() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
c.Header("X-Frame-Options", "DENY")
|
||||
c.Header("X-XSS-Protection", "1; mode=block")
|
||||
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
c.Header("Content-Security-Policy", "default-src 'self'")
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
45
internal/kernel/testutil/testutil.go
Normal file
45
internal/kernel/testutil/testutil.go
Normal file
@ -0,0 +1,45 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"mengstack/internal/config"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
// TestLogger returns a zap logger suitable for use in tests.
|
||||
func TestLogger(t *testing.T) *zap.Logger {
|
||||
return zaptest.NewLogger(t)
|
||||
}
|
||||
|
||||
// TestConfig returns a minimal config for unit tests.
|
||||
func TestConfig() *config.Config {
|
||||
return &config.Config{
|
||||
Server: config.ServerConfig{
|
||||
Port: 0,
|
||||
Mode: "test",
|
||||
},
|
||||
Database: config.DatabaseConfig{
|
||||
Host: "localhost",
|
||||
Port: 5432,
|
||||
User: "test",
|
||||
Password: "test",
|
||||
DBName: "mengstack_test",
|
||||
SSLMode: "disable",
|
||||
},
|
||||
Redis: config.RedisConfig{
|
||||
Addr: "localhost:6379",
|
||||
},
|
||||
JWT: config.JWTConfig{
|
||||
Secret: "test-secret",
|
||||
AccessExpiryMinutes: 30,
|
||||
RefreshExpiryDays: 7,
|
||||
Issuer: "test",
|
||||
},
|
||||
Log: config.LogConfig{
|
||||
Level: "debug",
|
||||
Format: "console",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,21 @@ func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
// ListLogs godoc
|
||||
// @Summary 查询审计日志
|
||||
// @Description 分页查询审计日志,支持按用户、操作、资源筛选
|
||||
// @Tags Audit
|
||||
// @Produce json
|
||||
// @Param user_id query int false "按用户 ID 筛选"
|
||||
// @Param action query string false "按操作筛选"
|
||||
// @Param resource query string false "按资源类型筛选"
|
||||
// @Param page query int false "页码(默认 1)"
|
||||
// @Param page_size query int false "每页条数(默认 20)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/audit/logs [get]
|
||||
func (h *Handler) ListLogs(c *gin.Context) {
|
||||
tenantID, _ := tenant.FromContext(c.Request.Context())
|
||||
|
||||
|
||||
@ -19,6 +19,18 @@ func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
// Create godoc
|
||||
// @Summary 创建通知
|
||||
// @Description 向指定用户发送通知
|
||||
// @Tags Notification
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.CreateNotificationRequest true "通知内容"
|
||||
// @Success 200 {object} response.Response{data=domain.NotificationDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications [post]
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
var req domain.CreateNotificationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@ -34,6 +46,18 @@ func (h *Handler) Create(c *gin.Context) {
|
||||
response.Success(c, n)
|
||||
}
|
||||
|
||||
// Get godoc
|
||||
// @Summary 获取通知详情
|
||||
// @Description 根据 ID 获取指定通知的详细信息
|
||||
// @Tags Notification
|
||||
// @Produce json
|
||||
// @Param id path int true "通知 ID"
|
||||
// @Success 200 {object} response.Response{data=domain.NotificationDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/{id} [get]
|
||||
func (h *Handler) Get(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -53,6 +77,20 @@ func (h *Handler) Get(c *gin.Context) {
|
||||
response.Success(c, n)
|
||||
}
|
||||
|
||||
// ListByUser godoc
|
||||
// @Summary 获取用户通知列表
|
||||
// @Description 分页获取指定用户的通知列表
|
||||
// @Tags Notification
|
||||
// @Produce json
|
||||
// @Param userId path int true "用户 ID"
|
||||
// @Param page query int false "页码(默认 1)"
|
||||
// @Param page_size query int false "每页条数(默认 20)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/user/{userId} [get]
|
||||
func (h *Handler) ListByUser(c *gin.Context) {
|
||||
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
|
||||
if err != nil {
|
||||
@ -74,6 +112,17 @@ func (h *Handler) ListByUser(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// MarkAsRead godoc
|
||||
// @Summary 标记通知已读
|
||||
// @Description 将指定通知标记为已读
|
||||
// @Tags Notification
|
||||
// @Param id path int true "通知 ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/{id}/read [put]
|
||||
func (h *Handler) MarkAsRead(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -88,6 +137,16 @@ func (h *Handler) MarkAsRead(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// MarkAllAsRead godoc
|
||||
// @Summary 全部标记已读
|
||||
// @Description 将当前用户的所有通知标记为已读
|
||||
// @Tags Notification
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/read-all [put]
|
||||
func (h *Handler) MarkAllAsRead(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
tenantID := c.GetString("tenant_id")
|
||||
@ -98,6 +157,16 @@ func (h *Handler) MarkAllAsRead(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// CountUnread godoc
|
||||
// @Summary 获取未读通知数
|
||||
// @Description 获取当前用户的未读通知数量
|
||||
// @Tags Notification
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/unread-count [get]
|
||||
func (h *Handler) CountUnread(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
tenantID := c.GetString("tenant_id")
|
||||
@ -109,6 +178,17 @@ func (h *Handler) CountUnread(c *gin.Context) {
|
||||
response.Success(c, gin.H{"count": count})
|
||||
}
|
||||
|
||||
// Delete godoc
|
||||
// @Summary 删除通知
|
||||
// @Description 删除指定通知
|
||||
// @Tags Notification
|
||||
// @Param id path int true "通知 ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/notifications/{id} [delete]
|
||||
func (h *Handler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
|
||||
@ -16,6 +16,19 @@ func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
// CreateTenant godoc
|
||||
// @Summary 创建租户
|
||||
// @Description 创建新的租户(组织),需提供名称和 slug
|
||||
// @Tags Org
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.CreateTenantRequest true "租户信息"
|
||||
// @Success 200 {object} response.Response{data=domain.TenantDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 409 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/org/tenants [post]
|
||||
func (h *Handler) CreateTenant(c *gin.Context) {
|
||||
var req domain.CreateTenantRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@ -32,6 +45,16 @@ func (h *Handler) CreateTenant(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// ListTenants godoc
|
||||
// @Summary 列出所有租户
|
||||
// @Description 获取系统中的所有租户列表
|
||||
// @Tags Org
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]domain.TenantDTO}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/org/tenants [get]
|
||||
func (h *Handler) ListTenants(c *gin.Context) {
|
||||
dtos, err := h.svc.ListTenants(c.Request.Context())
|
||||
if err != nil {
|
||||
@ -42,6 +65,18 @@ func (h *Handler) ListTenants(c *gin.Context) {
|
||||
response.Success(c, dtos)
|
||||
}
|
||||
|
||||
// GetTenant godoc
|
||||
// @Summary 获取租户详情
|
||||
// @Description 根据 ID 获取指定租户的详细信息
|
||||
// @Tags Org
|
||||
// @Produce json
|
||||
// @Param id path string true "租户 ID (UUID)"
|
||||
// @Success 200 {object} response.Response{data=domain.TenantDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/org/tenants/{id} [get]
|
||||
func (h *Handler) GetTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@ -54,6 +89,20 @@ func (h *Handler) GetTenant(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// UpdateTenant godoc
|
||||
// @Summary 更新租户
|
||||
// @Description 更新租户的名称、slug 或状态
|
||||
// @Tags Org
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "租户 ID (UUID)"
|
||||
// @Param request body domain.UpdateTenantRequest true "更新内容"
|
||||
// @Success 200 {object} response.Response{data=domain.TenantDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/org/tenants/{id} [put]
|
||||
func (h *Handler) UpdateTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
@ -72,6 +121,17 @@ func (h *Handler) UpdateTenant(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// DeleteTenant godoc
|
||||
// @Summary 删除租户
|
||||
// @Description 删除指定租户及其所有关联数据
|
||||
// @Tags Org
|
||||
// @Param id path string true "租户 ID (UUID)"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/org/tenants/{id} [delete]
|
||||
func (h *Handler) DeleteTenant(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
|
||||
@ -18,6 +18,17 @@ func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
// ListPermissions godoc
|
||||
// @Summary 列出权限
|
||||
// @Description 获取所有权限列表,可按模块筛选
|
||||
// @Tags RBAC
|
||||
// @Produce json
|
||||
// @Param module query string false "按模块筛选"
|
||||
// @Success 200 {object} response.Response{data=[]domain.PermissionDTO}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/permissions [get]
|
||||
func (h *Handler) ListPermissions(c *gin.Context) {
|
||||
module := c.Query("module")
|
||||
|
||||
@ -40,6 +51,19 @@ func (h *Handler) ListPermissions(c *gin.Context) {
|
||||
response.Success(c, dtos)
|
||||
}
|
||||
|
||||
// CreateRole godoc
|
||||
// @Summary 创建角色
|
||||
// @Description 在当前租户下创建新角色
|
||||
// @Tags RBAC
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.CreateRoleRequest true "角色信息"
|
||||
// @Success 200 {object} response.Response{data=domain.RoleDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 409 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles [post]
|
||||
func (h *Handler) CreateRole(c *gin.Context) {
|
||||
var req domain.CreateRoleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@ -56,6 +80,16 @@ func (h *Handler) CreateRole(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// ListRoles godoc
|
||||
// @Summary 列出角色
|
||||
// @Description 获取当前租户下的所有角色
|
||||
// @Tags RBAC
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]domain.RoleDTO}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles [get]
|
||||
func (h *Handler) ListRoles(c *gin.Context) {
|
||||
dtos, err := h.svc.ListRoles(c.Request.Context())
|
||||
if err != nil {
|
||||
@ -66,6 +100,18 @@ func (h *Handler) ListRoles(c *gin.Context) {
|
||||
response.Success(c, dtos)
|
||||
}
|
||||
|
||||
// GetRole godoc
|
||||
// @Summary 获取角色详情
|
||||
// @Description 根据 ID 获取角色信息,包含权限列表
|
||||
// @Tags RBAC
|
||||
// @Produce json
|
||||
// @Param id path int true "角色 ID"
|
||||
// @Success 200 {object} response.Response{data=domain.RoleDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles/{id} [get]
|
||||
func (h *Handler) GetRole(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -82,6 +128,20 @@ func (h *Handler) GetRole(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// UpdateRole godoc
|
||||
// @Summary 更新角色
|
||||
// @Description 更新角色名称或描述
|
||||
// @Tags RBAC
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "角色 ID"
|
||||
// @Param request body domain.UpdateRoleRequest true "更新内容"
|
||||
// @Success 200 {object} response.Response{data=domain.RoleDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles/{id} [put]
|
||||
func (h *Handler) UpdateRole(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -104,6 +164,17 @@ func (h *Handler) UpdateRole(c *gin.Context) {
|
||||
response.Success(c, dto)
|
||||
}
|
||||
|
||||
// DeleteRole godoc
|
||||
// @Summary 删除角色
|
||||
// @Description 删除指定角色(系统角色不可删除)
|
||||
// @Tags RBAC
|
||||
// @Param id path int true "角色 ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 403 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles/{id} [delete]
|
||||
func (h *Handler) DeleteRole(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -119,6 +190,20 @@ func (h *Handler) DeleteRole(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// SetRolePermissions godoc
|
||||
// @Summary 设置角色权限
|
||||
// @Description 为角色批量设置权限(覆盖原有权限)
|
||||
// @Tags RBAC
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "角色 ID"
|
||||
// @Param request body domain.SetRolePermissionsRequest true "权限 ID 列表"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles/{id}/permissions [put]
|
||||
func (h *Handler) SetRolePermissions(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -140,6 +225,18 @@ func (h *Handler) SetRolePermissions(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// GetRolePermissions godoc
|
||||
// @Summary 获取角色权限列表
|
||||
// @Description 获取指定角色已分配的权限列表
|
||||
// @Tags RBAC
|
||||
// @Produce json
|
||||
// @Param id path int true "角色 ID"
|
||||
// @Success 200 {object} response.Response{data=[]domain.PermissionDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/roles/{id}/permissions [get]
|
||||
func (h *Handler) GetRolePermissions(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@ -156,6 +253,20 @@ func (h *Handler) GetRolePermissions(c *gin.Context) {
|
||||
response.Success(c, dto.Permissions)
|
||||
}
|
||||
|
||||
// AssignRole godoc
|
||||
// @Summary 分配角色给用户
|
||||
// @Description 为指定用户分配一个角色
|
||||
// @Tags RBAC
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param userId path int true "用户 ID"
|
||||
// @Param request body domain.AssignRoleRequest true "角色 ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 409 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/users/{userId}/roles [post]
|
||||
func (h *Handler) AssignRole(c *gin.Context) {
|
||||
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
|
||||
if err != nil {
|
||||
@ -177,6 +288,18 @@ func (h *Handler) AssignRole(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// RemoveRole godoc
|
||||
// @Summary 移除用户角色
|
||||
// @Description 从指定用户移除一个角色
|
||||
// @Tags RBAC
|
||||
// @Param userId path int true "用户 ID"
|
||||
// @Param roleId path int true "角色 ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/users/{userId}/roles/{roleId} [delete]
|
||||
func (h *Handler) RemoveRole(c *gin.Context) {
|
||||
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
|
||||
if err != nil {
|
||||
@ -198,6 +321,18 @@ func (h *Handler) RemoveRole(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// GetUserRoles godoc
|
||||
// @Summary 获取用户角色
|
||||
// @Description 获取指定用户的所有角色及权限
|
||||
// @Tags RBAC
|
||||
// @Produce json
|
||||
// @Param userId path int true "用户 ID"
|
||||
// @Success 200 {object} response.Response{data=[]domain.RoleDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/rbac/users/{userId}/roles [get]
|
||||
func (h *Handler) GetUserRoles(c *gin.Context) {
|
||||
userID, err := strconv.ParseUint(c.Param("userId"), 10, 64)
|
||||
if err != nil {
|
||||
|
||||
@ -18,6 +18,18 @@ func NewHandler(svc *application.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
// GetGlobalSetting godoc
|
||||
// @Summary 获取全局配置
|
||||
// @Description 根据 key 获取全局(非租户)配置项
|
||||
// @Tags Settings
|
||||
// @Produce json
|
||||
// @Param key path string true "配置键名"
|
||||
// @Success 200 {object} response.Response{data=domain.SettingDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/global/{key} [get]
|
||||
func (h *Handler) GetGlobalSetting(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
setting, err := h.svc.GetGlobalSetting(c.Request.Context(), key)
|
||||
@ -32,6 +44,19 @@ func (h *Handler) GetGlobalSetting(c *gin.Context) {
|
||||
response.Success(c, setting)
|
||||
}
|
||||
|
||||
// UpdateGlobalSetting godoc
|
||||
// @Summary 更新全局配置
|
||||
// @Description 创建或更新全局配置项
|
||||
// @Tags Settings
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param key path string true "配置键名"
|
||||
// @Param request body domain.UpdateSettingRequest true "配置值"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/global/{key} [put]
|
||||
func (h *Handler) UpdateGlobalSetting(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
var req domain.UpdateSettingRequest
|
||||
@ -47,6 +72,16 @@ func (h *Handler) UpdateGlobalSetting(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListGlobalSettings godoc
|
||||
// @Summary 列出全局配置
|
||||
// @Description 获取所有全局配置项
|
||||
// @Tags Settings
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]domain.SettingDTO}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings [get]
|
||||
func (h *Handler) ListGlobalSettings(c *gin.Context) {
|
||||
settings, err := h.svc.ListGlobalSettings(c.Request.Context())
|
||||
if err != nil {
|
||||
@ -56,6 +91,18 @@ func (h *Handler) ListGlobalSettings(c *gin.Context) {
|
||||
response.Success(c, settings)
|
||||
}
|
||||
|
||||
// GetTenantSetting godoc
|
||||
// @Summary 获取租户配置
|
||||
// @Description 根据 key 获取当前租户的配置项
|
||||
// @Tags Settings
|
||||
// @Produce json
|
||||
// @Param key path string true "配置键名"
|
||||
// @Success 200 {object} response.Response{data=domain.SettingDTO}
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/tenant/{key} [get]
|
||||
func (h *Handler) GetTenantSetting(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
tenantID := c.GetString("tenant_id")
|
||||
@ -71,6 +118,19 @@ func (h *Handler) GetTenantSetting(c *gin.Context) {
|
||||
response.Success(c, setting)
|
||||
}
|
||||
|
||||
// UpdateTenantSetting godoc
|
||||
// @Summary 更新租户配置
|
||||
// @Description 创建或更新当前租户的配置项
|
||||
// @Tags Settings
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param key path string true "配置键名"
|
||||
// @Param request body domain.UpdateSettingRequest true "配置值"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/tenant/{key} [put]
|
||||
func (h *Handler) UpdateTenantSetting(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
tenantID := c.GetString("tenant_id")
|
||||
@ -87,6 +147,16 @@ func (h *Handler) UpdateTenantSetting(c *gin.Context) {
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// ListTenantSettings godoc
|
||||
// @Summary 列出租户配置
|
||||
// @Description 获取当前租户的所有配置项
|
||||
// @Tags Settings
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=[]domain.SettingDTO}
|
||||
// @Failure 500 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/tenant [get]
|
||||
func (h *Handler) ListTenantSettings(c *gin.Context) {
|
||||
tenantID := c.GetString("tenant_id")
|
||||
settings, err := h.svc.ListTenantSettings(c.Request.Context(), tenantID)
|
||||
@ -97,6 +167,17 @@ func (h *Handler) ListTenantSettings(c *gin.Context) {
|
||||
response.Success(c, settings)
|
||||
}
|
||||
|
||||
// DeleteTenantSetting godoc
|
||||
// @Summary 删除租户配置
|
||||
// @Description 删除当前租户的指定配置项
|
||||
// @Tags Settings
|
||||
// @Param key path string true "配置键名"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.Response
|
||||
// @Failure 404 {object} response.Response
|
||||
// @Security Bearer
|
||||
// @Security TenantID
|
||||
// @Router /api/v1/settings/tenant/{key} [delete]
|
||||
func (h *Handler) DeleteTenantSetting(c *gin.Context) {
|
||||
key := c.Param("key")
|
||||
tenantID := c.GetString("tenant_id")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user