优化结构

This commit is contained in:
Yvan
2026-08-21 08:54:29 +08:00
parent c968f9f24e
commit 77e192b6c0
167 changed files with 3657 additions and 1528 deletions
Binary file not shown.
Binary file not shown.
+35
View File
@@ -0,0 +1,35 @@
server:
http:
network: tcp
addr: 127.0.0.1:18001
timeout: 60s
data:
database:
driver: sqlite
path: .tmp/init-check-data
name: kra-init
source: .tmp\init-check-data\kra-init.db
prefix: ""
log_mode: silent
config: ""
host: ""
password: ""
port: ""
user: ""
redis:
addr: ""
admin:
router_prefix: ""
jwt:
signing_key: 7bd3e9cb-e201-4982-9af9-2ea209a80179
expires_time: 3600s
buffer_time: 300s
issuer: kra
system:
disable_auto_migrate: false
local:
store_path: .tmp/init-check-data/uploads
path_prefix: uploads/file
app:
app_id: kra
env: test
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
server:
http:
network: tcp
addr: 127.0.0.1:18000
timeout: 60s
data:
database:
driver: sqlite
path: .tmp/init-smoke
name: fresh
source: ""
prefix: ""
log_mode: silent
redis:
addr: ""
admin:
router_prefix: ""
jwt:
signing_key: init-test-secret
expires_time: 3600s
buffer_time: 300s
issuer: kra
system:
disable_auto_migrate: false
local:
store_path: .tmp/init-smoke/uploads
path_prefix: uploads/file
app:
app_id: kra
env: test
+23 -9
View File
@@ -1,21 +1,35 @@
# System Module
系统模块承载当前管理后台的完整业务边界:
系统模块承载当前管理后台的完整业务边界。`internal` 顶层只保留有明确
生命周期或分层职责的包:
- `app`:组合根、system 模块定义和任务/路由运行时组合
- `biz`:系统领域对象、用例和仓储接口
- `conf`:配置 proto 与运行时配置解析
- `data`:数据库生命周期、系统仓储、系统表和支付持久化
- `service`:请求 DTO 与领域用例的转换
- `transport`:Gin server、middleware、handler、router、HTTP 响应工具
- `integration`:Redis、邮件、对象存储和支付渠道适配器
- `initialize`:数据库首次初始化和系统种子数据编排
- `worker`:定时任务执行与调度
- `integration`:Redis、邮件、对象存储、支付、WebSocket 和 EMQX 适配器
- `security`:后台 JWT 等安全实现
- `utils`:只服务于系统模块的无状态工具
- `server`:Gin server 组合与生命周期;横切 HTTP 代码按子包维护:
`server/handler`、`server/middleware`、`server/router`、`server/httpx`
- `service`:应用服务、DTO 与领域对象转换和路由元数据;DTO 集中在
`service/dto`,根包中的 `dto_aliases.go` 只负责兼容旧调用方
- `worker`:定时任务执行与调度
目录代表边界,模块文件按资源命名。DTO、handler、中间件、路由和 HTTP
响应工具分别放在独立子包中,避免 `service`/`server` 根目录堆积几十个
文件,同时不把只有一两个文件的业务逻辑再拆成新包。单文件的 module
定义并入 `app`,JWT 实现集中在 `security`,protobuf JSON 统一使用
`pkg/protoutil`。
`internal/app` 只有 `catalog.go` 和 `definition.go` 是有意保留的组合根:
前者组装模块 catalog、任务注册和运行时,后者声明 system 的迁移、管理面
和定时任务。它们不是可以下沉到 `service` 或 `data` 的业务文件。
系统表统一使用 `sys_` 前缀;业务表应由新业务模块自行命名和迁移,不要混入
本目录。
system 通过 `Definition()` 提供自己的迁移、支付菜单/API 和默认任务,通过
`worker.TaskMethods` 提供依赖系统用例的任务实现,通过 `transport/server.Routes`
提供路由。应用组合根消费这些公共协议;新增业务不需要修改 system 的初始化、
system 通过 `Definition()` 提供迁移、支付菜单/API 和默认任务,通过
`worker.TaskMethods` 提供依赖系统用例的任务实现,通过 `server/router.Routes` 提供
路由。应用组合根消费这些公共协议;新增业务不需要修改 system 的初始化、
worker、路由或数据层。
+3 -3
View File
@@ -10,10 +10,10 @@ import (
"strings"
"kra/app/system/internal/conf"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/worker"
"kra/pkg/logging"
"kra/pkg/mq"
"github.com/go-kratos/kratos/v3"
"github.com/go-kratos/kratos/v3/config"
@@ -40,10 +40,10 @@ func init() {
flag.StringVar(&flagconf, "conf", "../configs", "config path, eg: -conf config.yaml")
}
func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger) *kratos.App {
func newApp(logger *slog.Logger, hs *kratoshttp.Server, scheduler *worker.TaskScheduler, audit *service.AuditRecorder, loggerControl *logging.ReloadableLogger, _ mq.Client) *kratos.App {
if audit != nil && loggerControl != nil {
loggerControl.SetErrorSink(logging.ErrorSinkFunc(func(ctx context.Context, entry logging.ErrorEntry) error {
return audit.CreateErrorRequest(ctx, &dto.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID})
return audit.CreateErrorRequest(ctx, &service.ErrorRecordRequest{Form: entry.Form, Info: entry.Info, Level: entry.Level, RequestID: entry.RequestID, TraceID: entry.TraceID})
}))
}
return kratos.New(
+4
View File
@@ -16,6 +16,8 @@ import (
"kra/app/system/internal/integration"
"kra/app/system/internal/integration/cache"
"kra/app/system/internal/server"
"kra/app/system/internal/server/handler"
"kra/app/system/internal/server/router"
"kra/app/system/internal/service"
"kra/app/system/internal/worker"
"kra/pkg/logging"
@@ -29,6 +31,8 @@ import (
func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogger, string) (*kratos.App, func(), error) {
panic(wire.Build(
server.ProviderSet,
handler.ProviderSet,
router.ProviderSet,
worker.ProviderSet,
app.Catalog,
app.TaskRegistry,
+21 -5
View File
@@ -17,10 +17,13 @@ import (
"kra/app/system/internal/initialize"
"kra/app/system/internal/integration/cache"
"kra/app/system/internal/integration/email"
"kra/app/system/internal/integration/mq"
"kra/app/system/internal/integration/storage"
"kra/app/system/internal/integration/websocket"
"kra/app/system/internal/server"
"kra/app/system/internal/server/handler"
"kra/app/system/internal/server/router"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/handler"
"kra/app/system/internal/worker"
"kra/pkg/logging"
"log/slog"
@@ -143,14 +146,27 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
user := handler.NewUser(userService, authService)
navigation := handler.NewNavigation(userService)
session := handler.NewSession(tokenService)
set := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session)
routes := server.NewRoutes(set)
v := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session)
routes := router.NewRoutes(v)
taskMethods := worker.NewTaskMethods(taskUsecase, mediaUsecase, runtime)
moduleRuntime := app.Runtime(routes, taskMethods, registry)
engine := server.NewGinEngineWithRuntime(runtime, accessControlService, authService, securityService, auditRecorder, logger, string2, moduleRuntime)
websocketServer, cleanup2, err := websocket.New(runtime)
if err != nil {
cleanup()
return nil, nil, err
}
engine := server.NewGinEngineWithRuntime(runtime, accessControlService, authService, securityService, auditRecorder, logger, string2, moduleRuntime, websocketServer)
httpServer := server.NewGinServer(confServer, engine)
kratosApp := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger)
mqReloadable, cleanup3, err := mq.New(runtime, logger)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
kratosApp := newApp(logger, httpServer, taskScheduler, auditRecorder, reloadableLogger, mqReloadable)
return kratosApp, func() {
cleanup3()
cleanup2()
cleanup()
}, nil
}
+3 -4
View File
@@ -4,8 +4,7 @@
package app
import (
systemmodule "kra/app/system/internal/module"
systemserver "kra/app/system/internal/server"
systemrouter "kra/app/system/internal/server/router"
systemworker "kra/app/system/internal/worker"
"kra/pkg/module"
platformtask "kra/pkg/task"
@@ -15,7 +14,7 @@ import (
// module means adding one Definition here; system initialization and runtime
// code consume the catalog without knowing that module's implementation.
func Catalog() module.Catalog {
return module.Catalog{Definitions: []module.Definition{systemmodule.Definition()}}
return module.Catalog{Definitions: []module.Definition{Definition()}}
}
// TaskRegistry builds the process-wide registry from dependency-free module
@@ -28,7 +27,7 @@ func TaskRegistry(catalog module.Catalog) *platformtask.Registry {
}
// Runtime composes HTTP route contributors from the enabled modules.
func Runtime(systemRoutes *systemserver.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime {
func Runtime(systemRoutes *systemrouter.Routes, systemTasks *systemworker.TaskMethods, registry *platformtask.Registry) *module.Runtime {
platformtask.Apply(registry, systemTasks)
return module.NewRuntime(systemRoutes)
}
@@ -1,4 +1,4 @@
package system
package app
import (
datapayment "kra/app/system/internal/data/payment"
+44 -15
View File
@@ -493,6 +493,30 @@ func (uc *PaymentUsecase) Refund(ctx context.Context, provider, tradeNo string,
return uc.refundWithOrder(ctx, provider, tradeNo, amount)
}
// Fulfill retries delivery for an already-paid order. It is intentionally
// idempotent: the repository lease prevents concurrent attempts and a
// succeeded order is returned as a duplicate without invoking the handler.
func (uc *PaymentUsecase) Fulfill(ctx context.Context, provider, tradeNo string) (*PaymentResult, error) {
if !validPaymentText(provider, 64) || !validPaymentText(tradeNo, 128) {
return nil, errors.New("发货参数不完整")
}
if uc.orders == nil {
return nil, errors.New("支付订单仓储未接入")
}
order, err := uc.orders.FindPaymentOrder(ctx, provider, tradeNo)
if err != nil {
return nil, err
}
result := paymentResultFromOrder(order)
if result == nil {
return nil, ErrPaymentOrderNotFound
}
if order.PaymentStatus != PaymentStatusPaid {
return nil, ErrPaymentOrderState
}
return uc.fulfillOrder(ctx, order, result)
}
// Callback verifies the notification in the adapter, then always performs a
// provider-side query before it can dispatch fulfillment. Callback payloads
// are never trusted for amount, currency, or final payment state.
@@ -671,10 +695,21 @@ func (uc *PaymentUsecase) reconcilePaymentResult(ctx context.Context, result, ca
return result, nil
}
fulfilled, err := uc.fulfillOrder(ctx, order, result)
if err != nil {
return fail(err)
}
return fulfilled, nil
}
func (uc *PaymentUsecase) fulfillOrder(ctx context.Context, order *PaymentOrder, result *PaymentResult) (*PaymentResult, error) {
if order == nil || result == nil {
return nil, errors.New("支付发货订单为空")
}
order, token, duplicate, err := uc.orders.BeginPaymentFulfillment(ctx, result.Provider, result.TradeNo, 10*time.Minute)
if err != nil {
uc.logger.Error(ctx, "payment.order.fulfillment_begin_failed", "provider", result.Provider, "trade_no", result.TradeNo, "error", err)
return fail(err)
return nil, err
}
if duplicate {
result.Duplicate = true
@@ -685,29 +720,23 @@ func (uc *PaymentUsecase) reconcilePaymentResult(ctx context.Context, result, ca
if handler == nil {
err = fmt.Errorf("支付发货 handler 未注册: %s", order.BusinessType)
_, _ = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, false, err.Error())
uc.logger.Error(ctx, "payment.order.fulfillment_handler_missing", "provider", result.Provider, "trade_no", result.TradeNo, "business_type", order.BusinessType, "error", err)
return fail(err)
return nil, err
}
confirmation := &PaymentConfirmation{
ID: order.ConfirmationID, Provider: order.Provider, TradeNo: order.TradeNo,
ProviderTradeNo: result.ProviderTradeNo, BusinessType: order.BusinessType,
BusinessID: order.BusinessID, Subject: order.Subject, Amount: order.Amount,
OriginalAmount: order.OriginalAmount, PayerPaidAmount: order.PayerPaidAmount,
CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount,
confirmation := &PaymentConfirmation{ID: order.ConfirmationID, Provider: order.Provider, TradeNo: order.TradeNo,
ProviderTradeNo: result.ProviderTradeNo, BusinessType: order.BusinessType, BusinessID: order.BusinessID,
Subject: order.Subject, Amount: order.Amount, OriginalAmount: order.OriginalAmount,
PayerPaidAmount: order.PayerPaidAmount, CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount,
DiscountAmount: order.DiscountAmount, ProviderDiscountAmount: order.ProviderDiscountAmount,
MerchantDiscountAmount: order.MerchantDiscountAmount, SettlementAmount: order.SettlementAmount,
Currency: order.Currency, PayerCurrency: order.PayerCurrency, AmountBreakdownKnown: order.AmountBreakdownKnown,
Payload: result.Payload, CreatedAt: time.Now().UTC(),
}
Payload: result.Payload, CreatedAt: time.Now().UTC()}
if err = handler.Fulfill(ctx, confirmation); err != nil {
_, _ = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, false, err.Error())
uc.logger.Error(ctx, "payment.order.fulfillment_failed", "provider", result.Provider, "trade_no", result.TradeNo, "confirmation_id", confirmation.ID, "error", err)
return fail(err)
return nil, err
}
order, err = uc.orders.CompletePaymentFulfillment(ctx, result.Provider, result.TradeNo, token, true, "")
if err != nil {
uc.logger.Error(ctx, "payment.order.fulfillment_record_failed", "provider", result.Provider, "trade_no", result.TradeNo, "confirmation_id", confirmation.ID, "error", err)
return fail(err)
return nil, err
}
result.Status = "fulfilled"
return attachOrderResult(result, order), nil
+18
View File
@@ -330,6 +330,24 @@ func TestPersistentPaymentCallbackFulfillsAndPersistsDuplicate(t *testing.T) {
}
}
func TestPaymentFulfillRetriesFailedOrder(t *testing.T) {
orders := &paymentOrderRepoStub{order: &PaymentOrder{Provider: PaymentAlipay, TradeNo: "order-1", BusinessType: "game_item", BusinessID: "item-1", Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: PaymentStatusPaid, FulfillmentStatus: FulfillmentStatusFailed, RefundStatus: RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111"}}
uc := NewPaymentUsecase(&paymentRepoStub{}, orders, slog.New(slog.NewTextHandler(io.Discard, nil)))
handler := &paymentFulfillmentStub{}
registry := NewPaymentFulfillmentRegistry()
if err := registry.Register(handler); err != nil {
t.Fatal(err)
}
uc.SetFulfillmentRegistry(registry)
result, err := uc.Fulfill(context.Background(), PaymentAlipay, "order-1")
if err != nil {
t.Fatal(err)
}
if result.Status != "fulfilled" || handler.calls != 1 || orders.order.FulfillmentStatus != FulfillmentStatusSucceeded {
t.Fatalf("fulfillment retry failed: result=%+v calls=%d status=%s", result, handler.calls, orders.order.FulfillmentStatus)
}
}
func TestPaymentConfirmationAcceptsProviderDiscount(t *testing.T) {
intent := &PaymentIntent{Provider: PaymentAlipay, TradeNo: "T1", Amount: 1000, Currency: "CNY", BusinessType: "item", BusinessID: "I1"}
result := &PaymentResult{Provider: PaymentAlipay, TradeNo: "T1", ProviderTradeNo: "P1", Status: "success", Amount: 1000, PayerPaidAmount: 800, CashPaidAmount: 800, DiscountAmount: 200, ProviderDiscountAmount: 150, MerchantDiscountAmount: 50, SettlementAmount: 950, Currency: "CNY", AmountBreakdownKnown: true}
+296 -39
View File
@@ -219,6 +219,8 @@ type AdminBackend struct {
Zap *AdminBackend_Zap `protobuf:"bytes,11,opt,name=zap,proto3" json:"zap,omitempty"`
Cors *AdminBackend_CORS `protobuf:"bytes,12,opt,name=cors,proto3" json:"cors,omitempty"`
App *AdminBackend_App `protobuf:"bytes,13,opt,name=app,proto3" json:"app,omitempty"`
Websocket *AdminBackend_WebSocket `protobuf:"bytes,14,opt,name=websocket,proto3" json:"websocket,omitempty"`
Mq *AdminBackend_MQ `protobuf:"bytes,15,opt,name=mq,proto3" json:"mq,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -344,6 +346,20 @@ func (x *AdminBackend) GetApp() *AdminBackend_App {
return nil
}
func (x *AdminBackend) GetWebsocket() *AdminBackend_WebSocket {
if x != nil {
return x.Websocket
}
return nil
}
func (x *AdminBackend) GetMq() *AdminBackend_MQ {
if x != nil {
return x.Mq
}
return nil
}
type Server_HTTP struct {
state protoimpl.MessageState `protogen:"open.v1"`
Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"`
@@ -1704,6 +1720,216 @@ func (x *AdminBackend_App) GetEnv() string {
return ""
}
type AdminBackend_WebSocket struct {
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
AllowOrigins []string `protobuf:"bytes,3,rep,name=allow_origins,json=allowOrigins,proto3" json:"allow_origins,omitempty"`
MaxMessageSize int64 `protobuf:"varint,4,opt,name=max_message_size,json=maxMessageSize,proto3" json:"max_message_size,omitempty"`
WriteWait *durationpb.Duration `protobuf:"bytes,5,opt,name=write_wait,json=writeWait,proto3" json:"write_wait,omitempty"`
PongWait *durationpb.Duration `protobuf:"bytes,6,opt,name=pong_wait,json=pongWait,proto3" json:"pong_wait,omitempty"`
PingPeriod *durationpb.Duration `protobuf:"bytes,7,opt,name=ping_period,json=pingPeriod,proto3" json:"ping_period,omitempty"`
MessageBufferSize int32 `protobuf:"varint,8,opt,name=message_buffer_size,json=messageBufferSize,proto3" json:"message_buffer_size,omitempty"`
ConcurrentMessageHandling bool `protobuf:"varint,9,opt,name=concurrent_message_handling,json=concurrentMessageHandling,proto3" json:"concurrent_message_handling,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AdminBackend_WebSocket) Reset() {
*x = AdminBackend_WebSocket{}
mi := &file_conf_conf_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AdminBackend_WebSocket) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AdminBackend_WebSocket) ProtoMessage() {}
func (x *AdminBackend_WebSocket) ProtoReflect() protoreflect.Message {
mi := &file_conf_conf_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AdminBackend_WebSocket.ProtoReflect.Descriptor instead.
func (*AdminBackend_WebSocket) Descriptor() ([]byte, []int) {
return file_conf_conf_proto_rawDescGZIP(), []int{3, 11}
}
func (x *AdminBackend_WebSocket) GetEnabled() bool {
if x != nil {
return x.Enabled
}
return false
}
func (x *AdminBackend_WebSocket) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *AdminBackend_WebSocket) GetAllowOrigins() []string {
if x != nil {
return x.AllowOrigins
}
return nil
}
func (x *AdminBackend_WebSocket) GetMaxMessageSize() int64 {
if x != nil {
return x.MaxMessageSize
}
return 0
}
func (x *AdminBackend_WebSocket) GetWriteWait() *durationpb.Duration {
if x != nil {
return x.WriteWait
}
return nil
}
func (x *AdminBackend_WebSocket) GetPongWait() *durationpb.Duration {
if x != nil {
return x.PongWait
}
return nil
}
func (x *AdminBackend_WebSocket) GetPingPeriod() *durationpb.Duration {
if x != nil {
return x.PingPeriod
}
return nil
}
func (x *AdminBackend_WebSocket) GetMessageBufferSize() int32 {
if x != nil {
return x.MessageBufferSize
}
return 0
}
func (x *AdminBackend_WebSocket) GetConcurrentMessageHandling() bool {
if x != nil {
return x.ConcurrentMessageHandling
}
return false
}
// MQ config is persisted in sys_integration_configs (kind=mq/provider=emqx).
// The bootstrap fields are retained as a one-time migration source.
type AdminBackend_MQ struct {
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Broker string `protobuf:"bytes,2,opt,name=broker,proto3" json:"broker,omitempty"`
ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,5,opt,name=password,proto3" json:"password,omitempty"`
KeepAlive int32 `protobuf:"varint,6,opt,name=keep_alive,json=keepAlive,proto3" json:"keep_alive,omitempty"`
CleanSession bool `protobuf:"varint,7,opt,name=clean_session,json=cleanSession,proto3" json:"clean_session,omitempty"`
ConnectTimeout int32 `protobuf:"varint,8,opt,name=connect_timeout,json=connectTimeout,proto3" json:"connect_timeout,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AdminBackend_MQ) Reset() {
*x = AdminBackend_MQ{}
mi := &file_conf_conf_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AdminBackend_MQ) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AdminBackend_MQ) ProtoMessage() {}
func (x *AdminBackend_MQ) ProtoReflect() protoreflect.Message {
mi := &file_conf_conf_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AdminBackend_MQ.ProtoReflect.Descriptor instead.
func (*AdminBackend_MQ) Descriptor() ([]byte, []int) {
return file_conf_conf_proto_rawDescGZIP(), []int{3, 12}
}
func (x *AdminBackend_MQ) GetEnabled() bool {
if x != nil {
return x.Enabled
}
return false
}
func (x *AdminBackend_MQ) GetBroker() string {
if x != nil {
return x.Broker
}
return ""
}
func (x *AdminBackend_MQ) GetClientId() string {
if x != nil {
return x.ClientId
}
return ""
}
func (x *AdminBackend_MQ) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *AdminBackend_MQ) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *AdminBackend_MQ) GetKeepAlive() int32 {
if x != nil {
return x.KeepAlive
}
return 0
}
func (x *AdminBackend_MQ) GetCleanSession() bool {
if x != nil {
return x.CleanSession
}
return false
}
func (x *AdminBackend_MQ) GetConnectTimeout() int32 {
if x != nil {
return x.ConnectTimeout
}
return 0
}
type AdminBackend_Storage struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
@@ -1720,7 +1946,7 @@ type AdminBackend_Storage struct {
func (x *AdminBackend_Storage) Reset() {
*x = AdminBackend_Storage{}
mi := &file_conf_conf_proto_msgTypes[20]
mi := &file_conf_conf_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1732,7 +1958,7 @@ func (x *AdminBackend_Storage) String() string {
func (*AdminBackend_Storage) ProtoMessage() {}
func (x *AdminBackend_Storage) ProtoReflect() protoreflect.Message {
mi := &file_conf_conf_proto_msgTypes[20]
mi := &file_conf_conf_proto_msgTypes[22]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1745,7 +1971,7 @@ func (x *AdminBackend_Storage) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdminBackend_Storage.ProtoReflect.Descriptor instead.
func (*AdminBackend_Storage) Descriptor() ([]byte, []int) {
return file_conf_conf_proto_rawDescGZIP(), []int{3, 11}
return file_conf_conf_proto_rawDescGZIP(), []int{3, 13}
}
func (x *AdminBackend_Storage) GetType() string {
@@ -1819,7 +2045,7 @@ type AdminBackend_Qiniu struct {
func (x *AdminBackend_Qiniu) Reset() {
*x = AdminBackend_Qiniu{}
mi := &file_conf_conf_proto_msgTypes[21]
mi := &file_conf_conf_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1831,7 +2057,7 @@ func (x *AdminBackend_Qiniu) String() string {
func (*AdminBackend_Qiniu) ProtoMessage() {}
func (x *AdminBackend_Qiniu) ProtoReflect() protoreflect.Message {
mi := &file_conf_conf_proto_msgTypes[21]
mi := &file_conf_conf_proto_msgTypes[23]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1844,7 +2070,7 @@ func (x *AdminBackend_Qiniu) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdminBackend_Qiniu.ProtoReflect.Descriptor instead.
func (*AdminBackend_Qiniu) Descriptor() ([]byte, []int) {
return file_conf_conf_proto_rawDescGZIP(), []int{3, 12}
return file_conf_conf_proto_rawDescGZIP(), []int{3, 14}
}
func (x *AdminBackend_Qiniu) GetZone() string {
@@ -1916,7 +2142,7 @@ type AdminBackend_ObjectStore struct {
func (x *AdminBackend_ObjectStore) Reset() {
*x = AdminBackend_ObjectStore{}
mi := &file_conf_conf_proto_msgTypes[22]
mi := &file_conf_conf_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1928,7 +2154,7 @@ func (x *AdminBackend_ObjectStore) String() string {
func (*AdminBackend_ObjectStore) ProtoMessage() {}
func (x *AdminBackend_ObjectStore) ProtoReflect() protoreflect.Message {
mi := &file_conf_conf_proto_msgTypes[22]
mi := &file_conf_conf_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1941,7 +2167,7 @@ func (x *AdminBackend_ObjectStore) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdminBackend_ObjectStore.ProtoReflect.Descriptor instead.
func (*AdminBackend_ObjectStore) Descriptor() ([]byte, []int) {
return file_conf_conf_proto_rawDescGZIP(), []int{3, 13}
return file_conf_conf_proto_rawDescGZIP(), []int{3, 15}
}
func (x *AdminBackend_ObjectStore) GetEndpoint() string {
@@ -2086,7 +2312,7 @@ const file_conf_conf_proto_rawDesc = "" +
"\x12connect_timeout_ms\x18\n" +
" \x01(\x03R\x10connectTimeoutMs\x12\x15\n" +
"\x06is_zap\x18\v \x01(\bR\x05isZap\x120\n" +
"\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xad\x1c\n" +
"\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xc0\"\n" +
"\fAdminBackend\x12#\n" +
"\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" +
"\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" +
@@ -2102,7 +2328,9 @@ const file_conf_conf_proto_rawDesc = "" +
" \x01(\v2\x1f.kratos.api.AdminBackend.SystemR\x06system\x12.\n" +
"\x03zap\x18\v \x01(\v2\x1c.kratos.api.AdminBackend.ZapR\x03zap\x121\n" +
"\x04cors\x18\f \x01(\v2\x1d.kratos.api.AdminBackend.CORSR\x04cors\x12.\n" +
"\x03app\x18\r \x01(\v2\x1c.kratos.api.AdminBackend.AppR\x03app\x1a\xb8\x01\n" +
"\x03app\x18\r \x01(\v2\x1c.kratos.api.AdminBackend.AppR\x03app\x12@\n" +
"\twebsocket\x18\x0e \x01(\v2\".kratos.api.AdminBackend.WebSocketR\twebsocket\x12+\n" +
"\x02mq\x18\x0f \x01(\v2\x1b.kratos.api.AdminBackend.MQR\x02mq\x1a\xb8\x01\n" +
"\x03JWT\x12\x1f\n" +
"\vsigning_key\x18\x01 \x01(\tR\n" +
"signingKey\x12<\n" +
@@ -2175,7 +2403,29 @@ const file_conf_conf_proto_rawDesc = "" +
"\x03App\x12\x12\n" +
"\x04node\x18\x01 \x01(\tR\x04node\x12\x15\n" +
"\x06app_id\x18\x02 \x01(\tR\x05appId\x12\x10\n" +
"\x03env\x18\x03 \x01(\tR\x03env\x1a\xe8\x03\n" +
"\x03env\x18\x03 \x01(\tR\x03env\x1a\xa6\x03\n" +
"\tWebSocket\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12#\n" +
"\rallow_origins\x18\x03 \x03(\tR\fallowOrigins\x12(\n" +
"\x10max_message_size\x18\x04 \x01(\x03R\x0emaxMessageSize\x128\n" +
"\n" +
"write_wait\x18\x05 \x01(\v2\x19.google.protobuf.DurationR\twriteWait\x126\n" +
"\tpong_wait\x18\x06 \x01(\v2\x19.google.protobuf.DurationR\bpongWait\x12:\n" +
"\vping_period\x18\a \x01(\v2\x19.google.protobuf.DurationR\n" +
"pingPeriod\x12.\n" +
"\x13message_buffer_size\x18\b \x01(\x05R\x11messageBufferSize\x12>\n" +
"\x1bconcurrent_message_handling\x18\t \x01(\bR\x19concurrentMessageHandling\x1a\xf8\x01\n" +
"\x02MQ\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12\x16\n" +
"\x06broker\x18\x02 \x01(\tR\x06broker\x12\x1b\n" +
"\tclient_id\x18\x03 \x01(\tR\bclientId\x12\x1a\n" +
"\busername\x18\x04 \x01(\tR\busername\x12\x1a\n" +
"\bpassword\x18\x05 \x01(\tR\bpassword\x12\x1d\n" +
"\n" +
"keep_alive\x18\x06 \x01(\x05R\tkeepAlive\x12#\n" +
"\rclean_session\x18\a \x01(\bR\fcleanSession\x12'\n" +
"\x0fconnect_timeout\x18\b \x01(\x05R\x0econnectTimeout\x1a\xe8\x03\n" +
"\aStorage\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\x124\n" +
"\x05qiniu\x18\x02 \x01(\v2\x1e.kratos.api.AdminBackend.QiniuR\x05qiniu\x12C\n" +
@@ -2227,7 +2477,7 @@ func file_conf_conf_proto_rawDescGZIP() []byte {
return file_conf_conf_proto_rawDescData
}
var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_conf_conf_proto_goTypes = []any{
(*Bootstrap)(nil), // 0: kratos.api.Bootstrap
(*Server)(nil), // 1: kratos.api.Server
@@ -2249,10 +2499,12 @@ var file_conf_conf_proto_goTypes = []any{
(*AdminBackend_CORS)(nil), // 17: kratos.api.AdminBackend.CORS
(*AdminBackend_CORSRule)(nil), // 18: kratos.api.AdminBackend.CORSRule
(*AdminBackend_App)(nil), // 19: kratos.api.AdminBackend.App
(*AdminBackend_Storage)(nil), // 20: kratos.api.AdminBackend.Storage
(*AdminBackend_Qiniu)(nil), // 21: kratos.api.AdminBackend.Qiniu
(*AdminBackend_ObjectStore)(nil), // 22: kratos.api.AdminBackend.ObjectStore
(*durationpb.Duration)(nil), // 23: google.protobuf.Duration
(*AdminBackend_WebSocket)(nil), // 20: kratos.api.AdminBackend.WebSocket
(*AdminBackend_MQ)(nil), // 21: kratos.api.AdminBackend.MQ
(*AdminBackend_Storage)(nil), // 22: kratos.api.AdminBackend.Storage
(*AdminBackend_Qiniu)(nil), // 23: kratos.api.AdminBackend.Qiniu
(*AdminBackend_ObjectStore)(nil), // 24: kratos.api.AdminBackend.ObjectStore
(*durationpb.Duration)(nil), // 25: google.protobuf.Duration
}
var file_conf_conf_proto_depIdxs = []int32{
1, // 0: kratos.api.Bootstrap.server:type_name -> kratos.api.Server
@@ -2268,33 +2520,38 @@ var file_conf_conf_proto_depIdxs = []int32{
10, // 10: kratos.api.AdminBackend.captcha:type_name -> kratos.api.AdminBackend.Captcha
11, // 11: kratos.api.AdminBackend.local:type_name -> kratos.api.AdminBackend.Local
12, // 12: kratos.api.AdminBackend.email:type_name -> kratos.api.AdminBackend.Email
20, // 13: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage
22, // 13: kratos.api.AdminBackend.storage:type_name -> kratos.api.AdminBackend.Storage
13, // 14: kratos.api.AdminBackend.media:type_name -> kratos.api.AdminBackend.Media
14, // 15: kratos.api.AdminBackend.disk_list:type_name -> kratos.api.AdminBackend.Disk
15, // 16: kratos.api.AdminBackend.system:type_name -> kratos.api.AdminBackend.System
16, // 17: kratos.api.AdminBackend.zap:type_name -> kratos.api.AdminBackend.Zap
17, // 18: kratos.api.AdminBackend.cors:type_name -> kratos.api.AdminBackend.CORS
19, // 19: kratos.api.AdminBackend.app:type_name -> kratos.api.AdminBackend.App
23, // 20: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration
23, // 21: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration
23, // 22: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration
7, // 23: kratos.api.Data.Mongo.hosts:type_name -> kratos.api.Data.MongoHost
23, // 24: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration
23, // 25: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration
23, // 26: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration
18, // 27: kratos.api.AdminBackend.CORS.whitelist:type_name -> kratos.api.AdminBackend.CORSRule
21, // 28: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu
22, // 29: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore
22, // 30: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore
22, // 31: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore
22, // 32: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore
22, // 33: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore
22, // 34: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore
35, // [35:35] is the sub-list for method output_type
35, // [35:35] is the sub-list for method input_type
35, // [35:35] is the sub-list for extension type_name
35, // [35:35] is the sub-list for extension extendee
0, // [0:35] is the sub-list for field type_name
20, // 20: kratos.api.AdminBackend.websocket:type_name -> kratos.api.AdminBackend.WebSocket
21, // 21: kratos.api.AdminBackend.mq:type_name -> kratos.api.AdminBackend.MQ
25, // 22: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration
25, // 23: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration
25, // 24: kratos.api.Data.Redis.write_timeout:type_name -> google.protobuf.Duration
7, // 25: kratos.api.Data.Mongo.hosts:type_name -> kratos.api.Data.MongoHost
25, // 26: kratos.api.AdminBackend.JWT.expires_time:type_name -> google.protobuf.Duration
25, // 27: kratos.api.AdminBackend.JWT.buffer_time:type_name -> google.protobuf.Duration
25, // 28: kratos.api.AdminBackend.Captcha.store_expiration:type_name -> google.protobuf.Duration
18, // 29: kratos.api.AdminBackend.CORS.whitelist:type_name -> kratos.api.AdminBackend.CORSRule
25, // 30: kratos.api.AdminBackend.WebSocket.write_wait:type_name -> google.protobuf.Duration
25, // 31: kratos.api.AdminBackend.WebSocket.pong_wait:type_name -> google.protobuf.Duration
25, // 32: kratos.api.AdminBackend.WebSocket.ping_period:type_name -> google.protobuf.Duration
23, // 33: kratos.api.AdminBackend.Storage.qiniu:type_name -> kratos.api.AdminBackend.Qiniu
24, // 34: kratos.api.AdminBackend.Storage.aliyun_oss:type_name -> kratos.api.AdminBackend.ObjectStore
24, // 35: kratos.api.AdminBackend.Storage.huawei_obs:type_name -> kratos.api.AdminBackend.ObjectStore
24, // 36: kratos.api.AdminBackend.Storage.tencent_cos:type_name -> kratos.api.AdminBackend.ObjectStore
24, // 37: kratos.api.AdminBackend.Storage.aws_s3:type_name -> kratos.api.AdminBackend.ObjectStore
24, // 38: kratos.api.AdminBackend.Storage.cloudflare_r2:type_name -> kratos.api.AdminBackend.ObjectStore
24, // 39: kratos.api.AdminBackend.Storage.minio:type_name -> kratos.api.AdminBackend.ObjectStore
40, // [40:40] is the sub-list for method output_type
40, // [40:40] is the sub-list for method input_type
40, // [40:40] is the sub-list for extension type_name
40, // [40:40] is the sub-list for extension extendee
0, // [0:40] is the sub-list for field type_name
}
func init() { file_conf_conf_proto_init() }
@@ -2308,7 +2565,7 @@ func file_conf_conf_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_conf_conf_proto_rawDesc), len(file_conf_conf_proto_rawDesc)),
NumEnums: 0,
NumMessages: 23,
NumMessages: 25,
NumExtensions: 0,
NumServices: 0,
},
+27
View File
@@ -93,6 +93,8 @@ message AdminBackend {
Zap zap = 11;
CORS cors = 12;
App app = 13;
WebSocket websocket = 14;
MQ mq = 15;
message JWT {
string signing_key = 1;
@@ -179,6 +181,31 @@ message AdminBackend {
string env = 3;
}
message WebSocket {
bool enabled = 1;
string path = 2;
repeated string allow_origins = 3;
int64 max_message_size = 4;
google.protobuf.Duration write_wait = 5;
google.protobuf.Duration pong_wait = 6;
google.protobuf.Duration ping_period = 7;
int32 message_buffer_size = 8;
bool concurrent_message_handling = 9;
}
// MQ config is persisted in sys_integration_configs (kind=mq/provider=emqx).
// The bootstrap fields are retained as a one-time migration source.
message MQ {
bool enabled = 1;
string broker = 2;
string client_id = 3;
string username = 4;
string password = 5;
int32 keep_alive = 6;
bool clean_session = 7;
int32 connect_timeout = 8;
}
message Storage {
string type = 1;
Qiniu qiniu = 2;
+27 -1
View File
@@ -182,6 +182,8 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
fileAdmin := cloneAdminConfig(adminConfig)
fileAdmin.Storage = nil
fileAdmin.Email = nil
fileAdmin.Websocket = nil
fileAdmin.Mq = nil
adminValue, err := protoMap(fileAdmin)
if err != nil {
return err
@@ -194,6 +196,8 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
}
deleteYAMLMapping(&document, "admin", "storage")
deleteYAMLMapping(&document, "admin", "email")
deleteYAMLMapping(&document, "admin", "websocket")
deleteYAMLMapping(&document, "admin", "mq")
if adminConfig.System != nil {
if err = setServerHTTPPort(&document, adminConfig.System.Addr); err != nil {
return err
@@ -253,6 +257,8 @@ func (d *Data) persistDatabaseConfig(database *conf.Data_Database, signingKey st
}
deleteYAMLMapping(&document, "admin", "storage")
deleteYAMLMapping(&document, "admin", "email")
deleteYAMLMapping(&document, "admin", "websocket")
deleteYAMLMapping(&document, "admin", "mq")
return writeConfigDocument(configPath, &document)
}
@@ -271,11 +277,13 @@ func (d *Data) removeIntegrationConfigFromFile() error {
if err = yaml.Unmarshal(raw, &document); err != nil {
return err
}
if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil {
if yamlMappingValue(&document, "admin", "storage") == nil && yamlMappingValue(&document, "admin", "email") == nil && yamlMappingValue(&document, "admin", "websocket") == nil && yamlMappingValue(&document, "admin", "mq") == nil {
return nil
}
deleteYAMLMapping(&document, "admin", "storage")
deleteYAMLMapping(&document, "admin", "email")
deleteYAMLMapping(&document, "admin", "websocket")
deleteYAMLMapping(&document, "admin", "mq")
return writeConfigDocument(configPath, &document)
}
@@ -357,6 +365,8 @@ func (d *Data) reloadConfig(ctx context.Context) error {
}
legacyStorage := next.Admin.Storage
legacyEmail := next.Admin.Email
legacyWebSocket := next.Admin.Websocket
legacyMQ := next.Admin.Mq
currentAdmin := d.runtime.Admin()
if legacyStorage == nil {
if currentAdmin != nil {
@@ -366,6 +376,12 @@ func (d *Data) reloadConfig(ctx context.Context) error {
if legacyEmail == nil && currentAdmin != nil {
legacyEmail = currentAdmin.Email
}
if legacyWebSocket == nil && currentAdmin != nil {
legacyWebSocket = currentAdmin.Websocket
}
if legacyMQ == nil && currentAdmin != nil {
legacyMQ = currentAdmin.Mq
}
storageConfig, err := resolveStorageIntegrationConfig(candidateDB.WithContext(ctx), legacyStorage)
if err != nil {
return fmt.Errorf("reload storage configuration: %w", err)
@@ -376,6 +392,16 @@ func (d *Data) reloadConfig(ctx context.Context) error {
return fmt.Errorf("reload email configuration: %w", err)
}
next.Admin.Email = emailConfig
websocketConfig, err := resolveWebSocketIntegrationConfig(candidateDB.WithContext(ctx), legacyWebSocket)
if err != nil {
return fmt.Errorf("reload websocket configuration: %w", err)
}
next.Admin.Websocket = websocketConfig
mqConfig, err := resolveMQIntegrationConfig(candidateDB.WithContext(ctx), legacyMQ)
if err != nil {
return fmt.Errorf("reload mq configuration: %w", err)
}
next.Admin.Mq = mqConfig
candidateStorage, err := storage.New(next.Admin)
if err != nil {
return fmt.Errorf("reload storage: %w", err)
+2
View File
@@ -58,6 +58,8 @@ func (d *Data) watchConfig() func() {
if current := d.runtime.Admin(); current != nil {
next.Admin.Storage = current.Storage
next.Admin.Email = current.Email
next.Admin.Mq = current.Mq
next.Admin.Websocket = current.Websocket
}
next.Admin.ConfigPath = absolute
d.runtime.Replace(next.Data, next.Admin)
+10
View File
@@ -207,6 +207,16 @@ func NewData(runtime *conf.Runtime, appLogger *slog.Logger, storageManager *stor
}
admin.Storage = storageConfig
admin.Email = emailConfig
websocketConfig, websocketErr := resolveWebSocketIntegrationConfig(db, admin.Websocket)
if websocketErr != nil {
return nil, nil, fmt.Errorf("load websocket integration configuration: %w", websocketErr)
}
admin.Websocket = websocketConfig
mqConfig, mqErr := resolveMQIntegrationConfig(db, admin.Mq)
if mqErr != nil {
return nil, nil, fmt.Errorf("load mq integration configuration: %w", mqErr)
}
admin.Mq = mqConfig
runtime.Replace(c, admin)
activeStorage, storageErr := storage.New(admin)
if storageErr != nil {
@@ -69,6 +69,12 @@ func (d *Data) PersistAdminConfig(ctx context.Context, raw []byte) error {
if next.Email == nil {
next.Email = currentAdmin.Email
}
if next.Websocket == nil {
next.Websocket = currentAdmin.Websocket
}
if next.Mq == nil {
next.Mq = currentAdmin.Mq
}
next.ConfigPath = currentAdmin.ConfigPath
candidateStorage, err := storage.New(next)
if err != nil {
@@ -80,6 +86,12 @@ func (d *Data) PersistAdminConfig(ctx context.Context, raw []byte) error {
if err := d.persistEmailIntegrationConfig(ctx, next.Email); err != nil {
return err
}
if err := d.PersistWebSocketConfig(ctx, next.Websocket); err != nil {
return err
}
if err := d.persistMQIntegrationConfig(ctx, next.Mq); err != nil {
return err
}
if err := d.persistConfigValues(currentData, next); err != nil {
return err
}
@@ -108,6 +120,12 @@ func (d *Data) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byt
if nextAdmin.Email == nil {
nextAdmin.Email = currentAdmin.Email
}
if nextAdmin.Websocket == nil {
nextAdmin.Websocket = currentAdmin.Websocket
}
if nextAdmin.Mq == nil {
nextAdmin.Mq = currentAdmin.Mq
}
nextAdmin.ConfigPath = currentAdmin.ConfigPath
candidateStorage, err := storage.New(nextAdmin)
if err != nil {
@@ -119,6 +137,12 @@ func (d *Data) PersistRuntimeConfig(ctx context.Context, dataRaw, adminRaw []byt
if err := d.persistEmailIntegrationConfig(ctx, nextAdmin.Email); err != nil {
return err
}
if err := d.PersistWebSocketConfig(ctx, nextAdmin.Websocket); err != nil {
return err
}
if err := d.persistMQIntegrationConfig(ctx, nextAdmin.Mq); err != nil {
return err
}
if err := d.persistConfigValues(nextData, nextAdmin); err != nil {
return err
}
@@ -205,6 +229,14 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *biz.DatabaseConfig
if err != nil {
return fmt.Errorf("initialize email integration configuration: %w", err)
}
var legacyWebSocket *conf.AdminBackend_WebSocket
if currentAdmin != nil {
legacyWebSocket = currentAdmin.Websocket
}
websocketConfig, err := resolveWebSocketIntegrationConfig(candidate.WithContext(ctx), legacyWebSocket)
if err != nil {
return fmt.Errorf("initialize websocket integration configuration: %w", err)
}
signingKey := uuid.NewString()
if err := d.persistDatabaseConfig(config, signingKey); err != nil {
return fmt.Errorf("persist database configuration: %w", err)
@@ -219,7 +251,13 @@ func (d *Data) InitializeDatabase(ctx context.Context, input *biz.DatabaseConfig
}
currentAdmin.Jwt.SigningKey = signingKey
currentAdmin.Storage = storageConfig
currentAdmin.Websocket = websocketConfig
currentAdmin.Email = emailConfig
mqConfig, err := resolveMQIntegrationConfig(candidate.WithContext(ctx), currentAdmin.Mq)
if err != nil {
return fmt.Errorf("initialize mq integration configuration: %w", err)
}
currentAdmin.Mq = mqConfig
d.runtime.Replace(currentData, currentAdmin)
activated = true
return nil
@@ -19,6 +19,7 @@ const (
integrationKindStorage = "storage"
integrationKindEmail = "email"
integrationKindPayment = "payment"
integrationKindMQ = "mq"
)
// integrationConfigPO stores credentials and provider-specific options for
@@ -34,6 +35,88 @@ type integrationConfigPO struct {
Config string `gorm:"type:text;not null"`
}
func defaultMQIntegrationConfig() *conf.AdminBackend_MQ {
return &conf.AdminBackend_MQ{CleanSession: true, KeepAlive: 30, ConnectTimeout: 10}
}
func saveMQIntegrationConfig(db *gorm.DB, config *conf.AdminBackend_MQ) error {
if config == nil {
config = defaultMQIntegrationConfig()
}
raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(config)
if err != nil {
return fmt.Errorf("encode emqx integration configuration: %w", err)
}
enabled := config.Enabled && strings.TrimSpace(config.Broker) != ""
clean := db.Session(&gorm.Session{NewDB: true})
var row integrationConfigPO
err = clean.Where("kind = ? AND provider = ?", integrationKindMQ, "emqx").First(&row).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
return clean.Create(&integrationConfigPO{Kind: integrationKindMQ, Provider: "emqx", Enabled: enabled, Config: string(raw)}).Error
case err != nil:
return err
default:
return clean.Model(&row).Updates(map[string]any{"enabled": enabled, "config": string(raw)}).Error
}
}
func loadMQIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_MQ, bool, error) {
var row integrationConfigPO
err := db.Session(&gorm.Session{NewDB: true}).Where("kind = ? AND provider = ?", integrationKindMQ, "emqx").First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
if !json.Valid([]byte(row.Config)) {
return nil, false, errors.New("invalid emqx integration configuration")
}
config := defaultMQIntegrationConfig()
if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), config); err != nil {
return nil, false, fmt.Errorf("decode emqx integration configuration: %w", err)
}
config.Enabled = row.Enabled
return config, true, nil
}
func resolveMQIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_MQ) (*conf.AdminBackend_MQ, error) {
clean := db.Session(&gorm.Session{NewDB: true})
if !clean.Migrator().HasTable(&integrationConfigPO{}) {
if legacy == nil {
return defaultMQIntegrationConfig(), nil
}
return proto.Clone(legacy).(*conf.AdminBackend_MQ), nil
}
loaded, found, err := loadMQIntegrationConfig(clean)
if err != nil {
return nil, err
}
if found {
return loaded, nil
}
if legacy == nil {
legacy = defaultMQIntegrationConfig()
}
if err = saveMQIntegrationConfig(clean, legacy); err != nil {
return nil, err
}
loaded, _, err = loadMQIntegrationConfig(clean)
return loaded, err
}
func (d *Data) persistMQIntegrationConfig(ctx context.Context, config *conf.AdminBackend_MQ) error {
if !d.databaseReady.Load() {
return errors.New("database is not initialized")
}
db := d.gormDB.WithContext(ctx)
if !db.Migrator().HasTable(&integrationConfigPO{}) {
return errors.New("integration configuration table does not exist")
}
return saveMQIntegrationConfig(db, config)
}
func (integrationConfigPO) TableName() string { return "sys_integration_configs" }
var storageProviderNames = []string{
@@ -123,6 +123,25 @@ func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
}
}
func TestMQIntegrationConfigRoundTrip(t *testing.T) {
db := openIntegrationConfigTestDB(t)
legacy := &conf.AdminBackend_MQ{Enabled: true, Broker: "mqtt://emqx.example.com:1883", ClientId: "system", Username: "app", Password: "secret", KeepAlive: 45, CleanSession: true, ConnectTimeout: 12}
if err := saveMQIntegrationConfig(db, legacy); err != nil {
t.Fatal(err)
}
loaded, found, err := loadMQIntegrationConfig(db)
if err != nil || !found {
t.Fatalf("found=%v err=%v", found, err)
}
if loaded.Broker != legacy.Broker || loaded.Password != legacy.Password || loaded.KeepAlive != 45 {
t.Fatalf("loaded mq = %#v", loaded)
}
loaded, err = resolveMQIntegrationConfig(db, &conf.AdminBackend_MQ{Broker: "must-not-replace"})
if err != nil || loaded.Broker != legacy.Broker {
t.Fatalf("database mq was replaced: %#v err=%v", loaded, err)
}
}
func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
db := openIntegrationConfigTestDB(t)
legacy := &conf.AdminBackend_Storage{
@@ -186,7 +205,7 @@ func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n extension_key: retained\n")
input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n mq:\n enabled: true\n broker: tcp://localhost:1883\n password: legacy-mq-secret\n extension_key: retained\n")
if err := os.WriteFile(path, input, 0o600); err != nil {
t.Fatal(err)
}
@@ -199,6 +218,7 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"},
},
Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
Mq: &conf.AdminBackend_MQ{Enabled: true, Broker: "tcp://emqx:1883", Password: "database-only-mq-secret"},
}
if err := d.persistConfigValues(&conf.Data{}, admin); err != nil {
t.Fatal(err)
@@ -222,6 +242,9 @@ func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
if _, exists := adminValue["email"]; exists {
t.Fatalf("email remained in YAML: %s", raw)
}
if _, exists := adminValue["mq"]; exists {
t.Fatalf("mq remained in YAML: %s", raw)
}
if adminValue["extension_key"] != "retained" {
t.Fatalf("extension key was not retained: %#v", adminValue)
}
@@ -8,7 +8,7 @@ import (
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/security/adminauth"
"kra/app/system/internal/security"
jwt "github.com/golang-jwt/jwt/v5"
)
@@ -95,7 +95,7 @@ func (i *tokenIssuer) IssueToken(user *biz.User, authorityID uint, mustChangePas
if user.PasswordUpdatedAt != nil {
passwordVersion = user.PasswordUpdatedAt.UnixNano()
}
token, claims, err := adminauth.Generate(settings.SigningKey, settings.Issuer, expires, settings.Buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, mustChangePassword, passwordVersion)
token, claims, err := security.Generate(settings.SigningKey, settings.Issuer, expires, settings.Buffer, user.ID, authorityID, user.UUID, user.Username, user.NickName, mustChangePassword, passwordVersion)
if err != nil {
return nil, err
}
@@ -107,7 +107,7 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b
return nil, errors.New("nil JWT claims")
}
settings := i.settings.JWTSettings()
claims := &adminauth.Claims{
claims := &security.Claims{
UUID: source.UUID, ID: source.ID, Username: source.Username, NickName: source.NickName,
AuthorityID: authorityID, BufferTime: int64(source.BufferTime / time.Second), UserType: source.UserType,
MustChangePwd: source.MustChangePwd, PasswordVersion: source.PasswordVersion,
@@ -116,7 +116,7 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b
IssuedAt: jwt.NewNumericDate(source.IssuedAt), NotBefore: jwt.NewNumericDate(source.NotBefore), ExpiresAt: jwt.NewNumericDate(source.ExpiresAt),
},
}
token, err := adminauth.Sign(settings.SigningKey, claims)
token, err := security.Sign(settings.SigningKey, claims)
if err != nil {
return nil, err
}
@@ -124,16 +124,16 @@ func (i *tokenIssuer) ReissueToken(source *biz.AuthClaims, authorityID uint) (*b
}
func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) {
claims, err := adminauth.Parse(token, i.settings.JWTSettings().SigningKey)
claims, err := security.Parse(token, i.settings.JWTSettings().SigningKey)
if err != nil {
switch {
case errors.Is(err, adminauth.ErrTokenExpired):
case errors.Is(err, security.ErrTokenExpired):
return nil, biz.ErrTokenExpired
case errors.Is(err, adminauth.ErrTokenMalformed):
case errors.Is(err, security.ErrTokenMalformed):
return nil, biz.ErrTokenMalformed
case errors.Is(err, adminauth.ErrTokenSignatureInvalid):
case errors.Is(err, security.ErrTokenSignatureInvalid):
return nil, biz.ErrTokenSignatureInvalid
case errors.Is(err, adminauth.ErrTokenNotValidYet):
case errors.Is(err, security.ErrTokenNotValidYet):
return nil, biz.ErrTokenNotValidYet
default:
return nil, biz.ErrTokenInvalid
@@ -0,0 +1,96 @@
package data
import (
"context"
"encoding/json"
"errors"
"fmt"
"kra/app/system/internal/conf"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
)
const integrationKindWebSocket = "websocket"
// saveWebSocketIntegrationConfig persists the Melody settings in the shared
// integration table. It intentionally lives separately from storage/payment
// persistence so adding another transport does not expand their API surface.
func saveWebSocketIntegrationConfig(db *gorm.DB, config *conf.AdminBackend_WebSocket) error {
if config == nil {
config = &conf.AdminBackend_WebSocket{}
}
raw, err := protojson.MarshalOptions{UseProtoNames: true, EmitDefaultValues: true}.Marshal(config)
if err != nil {
return fmt.Errorf("encode websocket integration configuration: %w", err)
}
clean := db.Session(&gorm.Session{NewDB: true})
var current integrationConfigPO
err = clean.Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(&current).Error
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
return clean.Create(&integrationConfigPO{Kind: integrationKindWebSocket, Provider: "melody", Enabled: config.Enabled, Config: string(raw)}).Error
case err != nil:
return err
default:
return clean.Model(&current).Updates(map[string]any{"enabled": config.Enabled, "config": string(raw)}).Error
}
}
func loadWebSocketIntegrationConfig(db *gorm.DB) (*conf.AdminBackend_WebSocket, bool, error) {
var row integrationConfigPO
err := db.Session(&gorm.Session{NewDB: true}).Where("kind = ? AND provider = ?", integrationKindWebSocket, "melody").First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
if !json.Valid([]byte(row.Config)) {
return nil, false, errors.New("invalid websocket integration configuration")
}
config := &conf.AdminBackend_WebSocket{}
if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal([]byte(row.Config), config); err != nil {
return nil, false, fmt.Errorf("decode websocket integration configuration: %w", err)
}
config.Enabled = row.Enabled
return config, true, nil
}
func resolveWebSocketIntegrationConfig(db *gorm.DB, legacy *conf.AdminBackend_WebSocket) (*conf.AdminBackend_WebSocket, error) {
clean := db.Session(&gorm.Session{NewDB: true})
if !clean.Migrator().HasTable(&integrationConfigPO{}) {
if legacy == nil {
return &conf.AdminBackend_WebSocket{}, nil
}
return proto.Clone(legacy).(*conf.AdminBackend_WebSocket), nil
}
config, found, err := loadWebSocketIntegrationConfig(clean)
if err != nil {
return nil, err
}
if found {
return config, nil
}
if legacy == nil {
legacy = &conf.AdminBackend_WebSocket{}
}
if err = saveWebSocketIntegrationConfig(clean, legacy); err != nil {
return nil, err
}
config, _, err = loadWebSocketIntegrationConfig(clean)
return config, err
}
func (d *Data) PersistWebSocketConfig(ctx context.Context, config *conf.AdminBackend_WebSocket) error {
if !d.databaseReady.Load() {
return errors.New("database is not initialized")
}
db := d.gormDB.WithContext(ctx)
if !db.Migrator().HasTable(&integrationConfigPO{}) {
return errors.New("integration configuration table does not exist")
}
return saveWebSocketIntegrationConfig(db, config)
}
@@ -0,0 +1,50 @@
package data
import (
"testing"
"time"
"kra/app/system/internal/conf"
"google.golang.org/protobuf/types/known/durationpb"
)
func TestWebSocketIntegrationConfigRoundTrip(t *testing.T) {
db := openIntegrationConfigTestDB(t)
want := &conf.AdminBackend_WebSocket{
Enabled: true, Path: "/events", AllowOrigins: []string{"https://admin.example.com"},
MaxMessageSize: 4096, WriteWait: durationpb.New(3 * time.Second),
PongWait: durationpb.New(20 * time.Second), PingPeriod: durationpb.New(15 * time.Second),
MessageBufferSize: 32, ConcurrentMessageHandling: true,
}
if err := saveWebSocketIntegrationConfig(db, want); err != nil {
t.Fatal(err)
}
got, found, err := loadWebSocketIntegrationConfig(db)
if err != nil {
t.Fatal(err)
}
if !found {
t.Fatal("websocket integration configuration was not found")
}
if !got.Enabled || got.Path != want.Path || got.MaxMessageSize != want.MaxMessageSize || got.MessageBufferSize != want.MessageBufferSize {
t.Fatalf("loaded websocket config = %#v", got)
}
if len(got.AllowOrigins) != 1 || got.AllowOrigins[0] != want.AllowOrigins[0] {
t.Fatalf("allow origins = %v", got.AllowOrigins)
}
}
func TestResolveWebSocketIntegrationConfigPrefersDatabase(t *testing.T) {
db := openIntegrationConfigTestDB(t)
if err := saveWebSocketIntegrationConfig(db, &conf.AdminBackend_WebSocket{Enabled: true, Path: "/database"}); err != nil {
t.Fatal(err)
}
got, err := resolveWebSocketIntegrationConfig(db, &conf.AdminBackend_WebSocket{Path: "/legacy"})
if err != nil {
t.Fatal(err)
}
if got.Path != "/database" || !got.Enabled {
t.Fatalf("resolved websocket config = %#v", got)
}
}
@@ -5,7 +5,7 @@ import (
"encoding/json"
"kra/app/system/internal/conf"
"kra/app/system/internal/utils/configutil"
"kra/pkg/protoutil"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
@@ -89,6 +89,16 @@ func (r *Repo) ConfigurationJSON() (json.RawMessage, error) {
if adminConfig.App != nil {
admin["app"] = adminConfig.App
}
if adminConfig.Websocket != nil {
admin["websocket"] = adminConfig.Websocket
}
if adminConfig.Mq != nil {
mqConfig := proto.Clone(adminConfig.Mq).(*conf.AdminBackend_MQ)
if mqConfig.Password != "" {
mqConfig.Password = "******"
}
admin["mq"] = mqConfig
}
}
// Never mask secrets on the live runtime object. ConfigurationJSON is a
// read-only operation; mutating dataConfig here would replace the actual
@@ -105,6 +115,9 @@ func (r *Repo) ConfigurationJSON() (json.RawMessage, error) {
safeAdmin.Email.Secret = "******"
}
maskStorageSecrets(safeAdmin.Storage)
if safeAdmin.Mq != nil && safeAdmin.Mq.Password != "" {
safeAdmin.Mq.Password = "******"
}
}
dataMap := map[string]any{}
if safeData != nil {
@@ -137,12 +150,12 @@ func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) e
}
options := protojson.UnmarshalOptions{DiscardUnknown: true}
if len(value.Data) > 0 && string(value.Data) != "null" {
if err := configutil.MergeProtoJSON(nextData, value.Data, options); err != nil {
if err := protoutil.MergeProtoJSON(nextData, value.Data, options); err != nil {
return err
}
}
if len(value.Admin) > 0 && string(value.Admin) != "null" {
if err := configutil.MergeProtoJSON(nextAdmin, value.Admin, options); err != nil {
if err := protoutil.MergeProtoJSON(nextAdmin, value.Admin, options); err != nil {
return err
}
}
@@ -307,6 +320,9 @@ func preserveAdminSecrets(next, current *conf.AdminBackend) {
next.Email.Secret = current.Email.Secret
}
preserveStorageSecrets(next.Storage, current.Storage)
if next.Mq != nil && current.Mq != nil && maskedSecret(next.Mq.Password) {
next.Mq.Password = current.Mq.Password
}
}
func maskedSecret(value string) bool { return value == "" || value == "******" }
@@ -1,10 +1,11 @@
package configutil
package initialize
import (
"encoding/json"
"testing"
"kra/app/system/internal/conf"
"kra/pkg/protoutil"
"google.golang.org/protobuf/encoding/protojson"
)
@@ -16,7 +17,7 @@ func TestMergeProtoJSONPreservesOmittedFieldsAndNormalizesKeys(t *testing.T) {
}
patch := json.RawMessage(`{"router_prefix":"/admin","system":{"iplimit_count":9}}`)
if err := MergeProtoJSON(target, patch, protojson.UnmarshalOptions{DiscardUnknown: true}); err != nil {
if err := protoutil.MergeProtoJSON(target, patch, protojson.UnmarshalOptions{DiscardUnknown: true}); err != nil {
t.Fatal(err)
}
if target.RouterPrefix != "/admin" || target.System.IplimitCount != 9 {
+31
View File
@@ -8,8 +8,11 @@ state, or translate provider-specific protocols into `biz` interfaces.
- `cache`: Redis-backed cache with an in-memory fallback.
- `email`: SMTP email repository.
- `mq`: reloadable EMQX/MQTT client exposed through the shared `pkg/mq` interface.
- `payment`: payment-channel SDKs and callback/signature handling.
- `storage`: local and object-storage implementations of `biz.FileStorage`.
- `websocket`: reloadable Melody endpoint exposed through the shared
`pkg/websocket` wrapper.
These packages are not `utils`: they perform I/O and own external dependency
lifecycles. Their constructors are exposed through `ProviderSet`; `cmd` binds
@@ -18,3 +21,31 @@ the `cache.RedisProvider` implementation to the shared `data.Data` container.
Stateless protocol helpers that do not own clients live in
`pkg/paymentkit`. They are intentionally small and dependency
light, while provider adapters remain here.
## Shared WebSocket and MQ APIs
Business modules depend on `websocket.Hub` and `mq.Client` from the shared
`pkg/websocket` and `pkg/mq` packages; they do not construct
Melody or Paho clients and do not read system configuration directly. The
system integration packages own runtime refresh and shutdown. Registered
WebSocket handlers and MQTT subscriptions are retained when database-backed
configuration replaces a live client.
```go
ws.OnMessage(func(session *websocket.Session, payload []byte) {
_ = ws.Send(session, payload)
})
_ = ws.Broadcast([]byte(`{"type":"system.ready"}`))
_ = mq.PublishJSON(ctx, mqClient, "orders/paid", event, mq.AtLeastOnce, false)
_ = mqClient.Subscribe(ctx, "orders/+/paid", mq.AtLeastOnce, handler)
```
Integration configuration is stored in `sys_integration_configs`:
- WebSocket: `kind=websocket`, `provider=melody`
- EMQX: `kind=mq`, `provider=emqx`
The YAML values are only migration/bootstrap inputs. After the integration
table exists, the database is authoritative and runtime updates are applied
without restarting the process. The WebSocket public path defaults to `/ws`.
+154
View File
@@ -0,0 +1,154 @@
package mq
import (
"context"
"log/slog"
"sync"
"time"
"kra/app/system/internal/conf"
"kra/pkg/mq"
)
// Reloadable follows the runtime snapshot and keeps a single shared EMQX
// connection for all modules in this process.
type Reloadable struct {
mu sync.RWMutex
opMu sync.Mutex
current mq.Client
subscriptions map[string]subscription
stop func()
logger *slog.Logger
closed bool
}
type subscription struct {
qos byte
handler mq.Handler
}
func New(runtime *conf.Runtime, logger *slog.Logger) (*Reloadable, func(), error) {
if logger == nil {
logger = slog.Default()
}
r := &Reloadable{logger: logger, subscriptions: make(map[string]subscription)}
if runtime != nil {
var config *conf.AdminBackend_MQ
if admin := runtime.Admin(); admin != nil {
config = admin.GetMq()
}
r.replace(config)
r.stop = runtime.Subscribe(func(_ *conf.Data, admin *conf.AdminBackend) {
if admin != nil {
r.replace(admin.GetMq())
}
})
}
cleanup := func() {
if r.stop != nil {
r.stop()
}
_ = r.Close()
}
return r, cleanup, nil
}
func (r *Reloadable) replace(config *conf.AdminBackend_MQ) {
r.opMu.Lock()
defer r.opMu.Unlock()
if r.closed {
return
}
if config == nil {
config = &conf.AdminBackend_MQ{}
}
cfg := mq.Config{Enabled: config.Enabled, Broker: config.Broker, ClientID: config.ClientId, Username: config.Username, Password: config.Password, CleanSession: config.CleanSession}
if config.KeepAlive > 0 {
cfg.KeepAlive = time.Duration(config.KeepAlive) * time.Second
}
if config.ConnectTimeout > 0 {
cfg.ConnectTimeout = time.Duration(config.ConnectTimeout) * time.Second
}
client, err := mq.NewMQTT(cfg)
if err != nil {
r.logger.Warn("emqx unavailable", "mod", "mq", "error", err)
return
}
if config.Enabled {
if err = r.restoreSubscriptions(context.Background(), client); err != nil {
_ = client.Close()
r.logger.Warn("restore emqx subscriptions failed", "mod", "mq", "error", err)
return
}
}
r.mu.Lock()
old := r.current
r.current = client
r.mu.Unlock()
if old != nil {
_ = old.Close()
}
}
func (r *Reloadable) restoreSubscriptions(ctx context.Context, client mq.Client) error {
for topic, item := range r.subscriptions {
if err := client.Subscribe(ctx, topic, item.qos, item.handler); err != nil {
return err
}
}
return nil
}
func (r *Reloadable) client() mq.Client { r.mu.RLock(); defer r.mu.RUnlock(); return r.current }
func (r *Reloadable) Publish(ctx context.Context, topic string, payload []byte, qos byte, retain bool) error {
c := r.client()
if c == nil {
return mq.ErrUnavailable
}
return c.Publish(ctx, topic, payload, qos, retain)
}
func (r *Reloadable) Subscribe(ctx context.Context, topic string, qos byte, handler mq.Handler) error {
r.opMu.Lock()
defer r.opMu.Unlock()
c := r.client()
if c == nil {
return mq.ErrUnavailable
}
if err := c.Subscribe(ctx, topic, qos, handler); err != nil {
return err
}
r.subscriptions[topic] = subscription{qos: qos, handler: handler}
return nil
}
func (r *Reloadable) Unsubscribe(ctx context.Context, topics ...string) error {
r.opMu.Lock()
defer r.opMu.Unlock()
c := r.client()
if c == nil {
return mq.ErrUnavailable
}
if err := c.Unsubscribe(ctx, topics...); err != nil {
return err
}
for _, topic := range topics {
delete(r.subscriptions, topic)
}
return nil
}
func (r *Reloadable) Connected() bool { c := r.client(); return c != nil && c.Connected() }
func (r *Reloadable) Close() error {
r.opMu.Lock()
defer r.opMu.Unlock()
if r.closed {
return nil
}
r.closed = true
r.mu.Lock()
old := r.current
r.current = nil
r.mu.Unlock()
if old != nil {
return old.Close()
}
return nil
}
@@ -0,0 +1,56 @@
package mq
import (
"context"
"testing"
platformmq "kra/pkg/mq"
)
type fakeClient struct {
subscribed []string
unsubscribed []string
}
func (*fakeClient) Publish(context.Context, string, []byte, byte, bool) error { return nil }
func (f *fakeClient) Subscribe(_ context.Context, topic string, _ byte, _ platformmq.Handler) error {
f.subscribed = append(f.subscribed, topic)
return nil
}
func (f *fakeClient) Unsubscribe(_ context.Context, topics ...string) error {
f.unsubscribed = append(f.unsubscribed, topics...)
return nil
}
func (*fakeClient) Connected() bool { return true }
func (*fakeClient) Close() error { return nil }
func TestReloadableTracksSubscriptions(t *testing.T) {
client := &fakeClient{}
r := &Reloadable{current: client, subscriptions: make(map[string]subscription)}
handler := func(context.Context, platformmq.Message) {}
if err := r.Subscribe(context.Background(), "orders/+/paid", platformmq.AtLeastOnce, handler); err != nil {
t.Fatal(err)
}
if _, ok := r.subscriptions["orders/+/paid"]; !ok {
t.Fatal("subscription was not retained for configuration reload")
}
if err := r.Unsubscribe(context.Background(), "orders/+/paid"); err != nil {
t.Fatal(err)
}
if _, ok := r.subscriptions["orders/+/paid"]; ok {
t.Fatal("unsubscribed topic remained in the reload registry")
}
}
func TestReloadableRestoresSubscriptions(t *testing.T) {
client := &fakeClient{}
r := &Reloadable{subscriptions: map[string]subscription{
"orders/+/paid": {qos: platformmq.AtLeastOnce, handler: func(context.Context, platformmq.Message) {}},
}}
if err := r.restoreSubscriptions(context.Background(), client); err != nil {
t.Fatal(err)
}
if len(client.subscribed) != 1 || client.subscribed[0] != "orders/+/paid" {
t.Fatalf("restored subscriptions = %v", client.subscribed)
}
}
@@ -7,7 +7,11 @@ import (
"kra/app/system/internal/biz"
"kra/app/system/internal/integration/cache"
"kra/app/system/internal/integration/email"
mqintegration "kra/app/system/internal/integration/mq"
"kra/app/system/internal/integration/storage"
websocketintegration "kra/app/system/internal/integration/websocket"
"kra/pkg/mq"
platformws "kra/pkg/websocket"
"github.com/google/wire"
)
@@ -16,5 +20,9 @@ var ProviderSet = wire.NewSet(
cache.New,
email.NewEmailRepo,
storage.NewFileStorage,
mqintegration.New,
wire.Bind(new(mq.Client), new(*mqintegration.Reloadable)),
websocketintegration.New,
wire.Bind(new(platformws.Hub), new(*websocketintegration.Server)),
wire.Bind(new(biz.FileStorage), new(*storage.Reloadable)),
)
@@ -0,0 +1,260 @@
package websocket
import (
"errors"
"net/http"
"strings"
"sync"
"time"
melody "github.com/olahol/melody"
"kra/app/system/internal/conf"
platformws "kra/pkg/websocket"
)
// Server is the system-owned WebSocket endpoint. Business modules can attach
// handlers and publish messages without depending on Gin or Melody directly.
type Server struct {
mu sync.RWMutex
current *platformws.Server
path string
messageHandlers []func(*melody.Session, []byte)
binaryHandlers []func(*melody.Session, []byte)
connectHandlers []func(*melody.Session)
disconnectHandlers []func(*melody.Session)
closed bool
}
func New(runtime *conf.Runtime) (*Server, func(), error) {
s := &Server{}
s.Replace(runtime)
var unsubscribe func()
if runtime != nil {
unsubscribe = runtime.Subscribe(func(_ *conf.Data, _ *conf.AdminBackend) { s.Replace(runtime) })
}
return s, func() {
if unsubscribe != nil {
unsubscribe()
}
s.mu.Lock()
s.closed = true
current := s.current
s.current = nil
s.mu.Unlock()
if current != nil {
_ = current.Close()
}
}, nil
}
func (s *Server) Replace(runtime *conf.Runtime) {
var config *conf.AdminBackend_WebSocket
if runtime != nil && runtime.Admin() != nil {
config = runtime.Admin().Websocket
}
if config == nil {
config = &conf.AdminBackend_WebSocket{}
}
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return
}
previous := s.current
if !config.Enabled {
s.current = nil
s.path = ""
s.mu.Unlock()
if previous != nil {
_ = previous.Close()
}
return
}
next := platformws.New(platformws.Config{
WriteWait: duration(config.WriteWait, 10*time.Second),
PongWait: duration(config.PongWait, 60*time.Second),
PingPeriod: duration(config.PingPeriod, 54*time.Second),
MaxMessageSize: config.MaxMessageSize,
MessageBufferSize: int(config.MessageBufferSize),
ConcurrentMessageHandling: config.ConcurrentMessageHandling,
AllowOrigins: config.AllowOrigins,
})
for _, handler := range s.messageHandlers {
next.OnMessage(handler)
}
for _, handler := range s.binaryHandlers {
next.OnBinaryMessage(handler)
}
for _, handler := range s.connectHandlers {
next.OnConnect(handler)
}
for _, handler := range s.disconnectHandlers {
next.OnDisconnect(handler)
}
s.path = strings.TrimSpace(config.Path)
if s.path == "" {
s.path = "/ws"
} else if !strings.HasPrefix(s.path, "/") {
s.path = "/" + s.path
}
s.current = next
s.mu.Unlock()
if previous != nil {
_ = previous.Close()
}
}
func duration(value interface{ AsDuration() time.Duration }, fallback time.Duration) time.Duration {
if value == nil {
return fallback
}
if result := value.AsDuration(); result > 0 {
return result
}
return fallback
}
func (s *Server) Enabled() bool {
if s == nil {
return false
}
s.mu.RLock()
defer s.mu.RUnlock()
return s.current != nil
}
func (s *Server) Path() string {
if s == nil {
return ""
}
s.mu.RLock()
defer s.mu.RUnlock()
return s.path
}
func (s *Server) HandleRequest(w http.ResponseWriter, r *http.Request) error {
if s == nil {
return errors.New("websocket server is disabled")
}
s.mu.RLock()
current := s.current
s.mu.RUnlock()
if current == nil {
return errors.New("websocket server is disabled")
}
return current.HandleRequest(w, r)
}
func (s *Server) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]any) error {
current, err := s.active()
if err != nil {
return err
}
return current.HandleRequestWithKeys(w, r, keys)
}
func (s *Server) Broadcast(message []byte) error {
if s == nil {
return errors.New("websocket server is disabled")
}
s.mu.RLock()
current := s.current
s.mu.RUnlock()
if current == nil {
return errors.New("websocket server is disabled")
}
return current.Broadcast(message)
}
func (s *Server) BroadcastBinary(message []byte) error {
if s == nil {
return errors.New("websocket server is disabled")
}
s.mu.RLock()
current := s.current
s.mu.RUnlock()
if current == nil {
return errors.New("websocket server is disabled")
}
return current.BroadcastBinary(message)
}
func (s *Server) Sessions() ([]*melody.Session, error) {
current, err := s.active()
if err != nil {
return nil, err
}
return current.Sessions()
}
func (s *Server) Len() int {
current, err := s.active()
if err != nil {
return 0
}
return current.Len()
}
func (s *Server) Send(session *melody.Session, message []byte) error {
current, err := s.active()
if err != nil {
return err
}
return current.Send(session, message)
}
func (s *Server) SendBinary(session *melody.Session, message []byte) error {
current, err := s.active()
if err != nil {
return err
}
return current.SendBinary(session, message)
}
func (s *Server) active() (*platformws.Server, error) {
if s == nil {
return nil, errors.New("websocket server is disabled")
}
s.mu.RLock()
current := s.current
s.mu.RUnlock()
if current == nil {
return nil, errors.New("websocket server is disabled")
}
return current, nil
}
func (s *Server) OnMessage(handler func(*melody.Session, []byte)) {
if s == nil || handler == nil {
return
}
s.mu.Lock()
s.messageHandlers = append(s.messageHandlers, handler)
current := s.current
if current != nil {
current.OnMessage(handler)
}
s.mu.Unlock()
}
func (s *Server) OnBinaryMessage(handler func(*melody.Session, []byte)) {
if s == nil || handler == nil {
return
}
s.mu.Lock()
s.binaryHandlers = append(s.binaryHandlers, handler)
if s.current != nil {
s.current.OnBinaryMessage(handler)
}
s.mu.Unlock()
}
func (s *Server) OnConnect(handler func(*melody.Session)) {
if s == nil || handler == nil {
return
}
s.mu.Lock()
s.connectHandlers = append(s.connectHandlers, handler)
if s.current != nil {
s.current.OnConnect(handler)
}
s.mu.Unlock()
}
func (s *Server) OnDisconnect(handler func(*melody.Session)) {
if s == nil || handler == nil {
return
}
s.mu.Lock()
s.disconnectHandlers = append(s.disconnectHandlers, handler)
if s.current != nil {
s.current.OnDisconnect(handler)
}
s.mu.Unlock()
}
@@ -1,4 +1,4 @@
package adminauth
package security
import (
"errors"
+27 -7
View File
@@ -11,10 +11,12 @@ import (
"time"
"kra/app/system/internal/conf"
websocket "kra/app/system/internal/integration/websocket"
"kra/app/system/internal/server/handler"
"kra/app/system/internal/server/httpx"
"kra/app/system/internal/server/middleware"
"kra/app/system/internal/server/router"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/handler"
"kra/app/system/internal/transport/httpx"
servermiddleware "kra/app/system/internal/transport/middleware"
platformmodule "kra/pkg/module"
"github.com/gin-gonic/gin"
@@ -22,16 +24,16 @@ import (
)
func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(NewRoutes(handlers)))
return NewGinEngineWithRuntime(runtime, access, auth, security, audit, logger, version, platformmodule.NewRuntime(router.NewRoutes(handlers)), nil)
}
func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime) *gin.Engine {
func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessControlService, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string, routes *platformmodule.Runtime, ws *websocket.Server) *gin.Engine {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
if err := engine.SetTrustedProxies(nil); err != nil && logger != nil {
logger.Error("disable trusted proxies failed", "error", err)
}
engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(logger), servermiddleware.AccessLog(runtime, logger, version), servermiddleware.CORS(runtime), servermiddleware.ErrorAudit(logger), servermiddleware.SecurityRateLimit(security))
engine.Use(middleware.RequestMeta(), middleware.Recovery(logger), middleware.AccessLog(runtime, logger, version), middleware.CORS(runtime), middleware.ErrorAudit(logger), middleware.SecurityRateLimit(security))
prefix := ""
config := runtime.Admin()
@@ -46,14 +48,32 @@ func NewGinEngineWithRuntime(runtime *conf.Runtime, access *service.AccessContro
// password and Casbin/data-scope middleware. Keeping the recorder after the
// equivalent Kra guards avoids persisting rejected unauthenticated or
// unauthorized requests as successful business operations.
private.Use(servermiddleware.Auth(auth), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access), servermiddleware.OperationAudit(runtime, audit))
private.Use(middleware.Auth(auth), middleware.MustChangePassword(), middleware.AccessControl(runtime, access), middleware.OperationAudit(runtime, audit))
if routes != nil {
routes.RegisterRoutes(public, private, engine)
}
if ws != nil && ws.Enabled() {
path := ws.Path()
engine.GET(path, func(c *gin.Context) {
if !ws.Enabled() || c.Request.URL.Path != ws.Path() {
c.Status(http.StatusNotFound)
return
}
if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil {
logger.Warn("websocket request failed", "mod", "websocket", "error", err)
}
})
}
registerSwagger(engine, prefix, version, logger)
registerLocalStorage(engine, runtime)
engine.NoRoute(func(c *gin.Context) {
if ws != nil && ws.Enabled() && c.Request.Method == http.MethodGet && c.Request.URL.Path == ws.Path() {
if err := ws.HandleRequest(c.Writer, c.Request); err != nil && logger != nil {
logger.Warn("websocket request failed", "mod", "websocket", "error", err)
}
return
}
if serveLocalStorage(c, runtime) {
return
}
+6 -2
View File
@@ -12,7 +12,7 @@ import (
"testing"
"kra/app/system/internal/conf"
"kra/app/system/internal/transport/handler"
"kra/app/system/internal/server/handler"
"github.com/gin-gonic/gin"
)
@@ -56,7 +56,7 @@ func TestGinStartupLogsEveryRegisteredRoute(t *testing.T) {
if got, want := strings.Count(text, `"msg":"router registered"`), len(engine.Routes()); got != want {
t.Fatalf("registered route log count = %d, want %d", got, want)
}
if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":186`) {
if !strings.Contains(text, `"msg":"router register success"`) || !strings.Contains(text, `"route_count":190`) {
t.Fatalf("startup route summary is missing: %s", text)
}
}
@@ -258,6 +258,7 @@ GET /logViewer/files
GET /menu/getMenuRoles
GET /payment/configs
GET /payment/orders
GET /payment/orders/:provider/:tradeNo
GET /position/findPosition
GET /position/getPositionUsers
GET /securityConfig/getSecurityConfig
@@ -353,7 +354,10 @@ POST /menu/updateBaseMenu
POST /payment/callback/:provider
POST /payment/config
POST /payment/create
POST /payment/fulfill
POST /payment/order
POST /payment/orders/:provider/:tradeNo/fulfill
POST /payment/orders/:provider/:tradeNo/refund
POST /payment/query
POST /payment/refund
POST /position/createPosition
@@ -1,11 +1,8 @@
package handler
import (
"time"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"time"
"github.com/gin-gonic/gin"
)
@@ -16,86 +13,86 @@ func NewAnnouncement(service *service.AnnouncementService) *Announcement {
return &Announcement{service: service}
}
func announcementInput(req dto.AnnouncementRequest) service.AnnouncementInput {
func announcementInput(req service.AnnouncementRequest) service.AnnouncementInput {
return service.AnnouncementInput{ID: req.ID, Title: req.Title, Content: req.Content, UserID: req.UserID, Attachments: req.Attachments}
}
func (h *Announcement) Create(c *gin.Context) {
var req dto.AnnouncementRequest
var req service.AnnouncementRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Create(c.Request.Context(), announcementInput(req)); err != nil {
httpx.Fail(c, "创建失败")
Fail(c, "创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Announcement) Delete(c *gin.Context) {
if err := h.service.Delete(c.Request.Context(), c.Query("ID")); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Announcement) DeleteByIDs(c *gin.Context) {
if err := h.service.DeleteByIDs(c.Request.Context(), c.QueryArray("IDs[]")); err != nil {
httpx.Fail(c, "批量删除失败")
Fail(c, "批量删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Announcement) Update(c *gin.Context) {
var req dto.AnnouncementRequest
var req service.AnnouncementRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Update(c.Request.Context(), announcementInput(req)); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Announcement) Find(c *gin.Context) {
item, err := h.service.Find(c.Request.Context(), c.Query("ID"))
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.OKWithData(c, item)
OKWithData(c, item)
}
func (h *Announcement) List(c *gin.Context) {
var req dto.AnnouncementSearchRequest
var req service.AnnouncementSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.List(c.Request.Context(), req.Page, req.PageSize, req.StartCreatedAt, req.EndCreatedAt)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Announcement) DataSource(c *gin.Context) {
users, err := h.service.UserOptions(c.Request.Context())
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.OKWithData(c, gin.H{"userID": users})
OKWithData(c, gin.H{"userID": users})
}
func (h *Announcement) Public(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功")
Write(c, CodeSuccess, gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功")
}
func parseTime(value string) (*time.Time, error) {
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -13,234 +11,234 @@ type API struct{ service *service.APIService }
func NewAPI(service *service.APIService) *API { return &API{service: service} }
func (h *API) List(c *gin.Context) {
var req dto.APIListRequest
var req service.APIListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Page == 0 {
httpx.Fail(c, "Page值不能为空")
Fail(c, "Page值不能为空")
return
}
if req.PageSize == 0 {
httpx.Fail(c, "PageSize值不能为空")
Fail(c, "PageSize值不能为空")
return
}
items, total, err := h.service.ListAPI(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *API) All(c *gin.Context) {
items, err := h.service.AllAPI(c.Request.Context(), true)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"apis": items}, "获取成功")
Write(c, CodeSuccess, gin.H{"apis": items}, "获取成功")
}
func (h *API) Create(c *gin.Context) {
var req dto.APIRequest
var req service.APIRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if message := validateAPIRequest(&req); message != "" {
httpx.Fail(c, message)
Fail(c, message)
return
}
created, err := h.service.CreateAPIRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败")
Fail(c, "创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, created, "创建成功")
Write(c, CodeSuccess, created, "创建成功")
}
func (h *API) Update(c *gin.Context) {
var req dto.APIRequest
var req service.APIRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if message := validateAPIRequest(&req); message != "" {
httpx.Fail(c, message)
Fail(c, message)
return
}
if err := h.service.UpdateAPIRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "修改失败")
Fail(c, "修改失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功")
Write(c, CodeSuccess, gin.H{}, "修改成功")
}
func (h *API) Delete(c *gin.Context) {
var req dto.DeleteAPIRequest
var req service.DeleteAPIRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
if err := h.service.DeleteAPI(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *API) DeleteByIDs(c *gin.Context) {
var req dto.DeleteAPIsRequest
var req service.DeleteAPIsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteAPIs(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *API) Find(c *gin.Context) {
var req dto.GetAPIRequest
var req service.GetAPIRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
item, err := h.service.FindAPIResponse(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"api": item}, "获取成功")
Write(c, CodeSuccess, gin.H{"api": item}, "获取成功")
}
func (h *API) Groups(c *gin.Context) {
groups, groupAPIMap, err := h.service.Groups(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.OKWithData(c, gin.H{"groups": groups, "apiGroupMap": groupAPIMap})
OKWithData(c, gin.H{"groups": groups, "apiGroupMap": groupAPIMap})
}
func (h *API) Roles(c *gin.Context) {
path, method := c.Query("path"), c.Query("method")
if path == "" || method == "" {
httpx.Fail(c, "API路径和请求方法不能为空")
Fail(c, "API路径和请求方法不能为空")
return
}
ids, err := h.service.APIRoleIDs(c.Request.Context(), path, method)
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
if ids == nil {
ids = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
Write(c, CodeSuccess, ids, "获取成功")
}
func (h *API) SetRoles(c *gin.Context) {
var req dto.SetAPIRolesRequest
var req service.SetAPIRolesRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Path == "" || req.Method == "" {
httpx.Fail(c, "API路径和请求方法不能为空")
Fail(c, "API路径和请求方法不能为空")
return
}
if err := h.service.SetAPIRoles(c.Request.Context(), req.Path, req.Method, req.AuthorityIDs); err != nil {
httpx.Fail(c, "设置失败"+err.Error())
Fail(c, "设置失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *API) Sync(engine *gin.Engine) gin.HandlerFunc {
return func(c *gin.Context) {
routes := engine.Routes()
values := make([]dto.APIRequest, 0, len(routes))
values := make([]service.APIRequest, 0, len(routes))
for _, route := range routes {
values = append(values, dto.APIRequest{Path: route.Path, Method: route.Method})
values = append(values, service.APIRequest{Path: route.Path, Method: route.Method})
}
result, err := h.service.SyncAPIResponses(c.Request.Context(), values)
if err != nil {
httpx.Fail(c, "同步失败")
Fail(c, "同步失败")
return
}
httpx.OKWithData(c, result)
OKWithData(c, result)
}
}
func (h *API) Ignore(c *gin.Context) {
var req dto.IgnoreAPIRequest
var req service.IgnoreAPIRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.SetAPIIgnored(c.Request.Context(), req.Path, req.Method, req.Flag); err != nil {
httpx.Fail(c, "忽略失败")
Fail(c, "忽略失败")
return
}
httpx.OK(c)
OK(c)
}
func (h *API) ApplySync(c *gin.Context) {
var req dto.ApplyAPISyncRequest
var req service.ApplyAPISyncRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.ApplyAPISyncRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "忽略失败")
Fail(c, "忽略失败")
return
}
httpx.OK(c)
OK(c)
}
func (h *API) FreshCasbin(c *gin.Context) {
if err := h.service.FreshCasbin(c.Request.Context()); err != nil {
httpx.Fail(c, "刷新失败")
Fail(c, "刷新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "刷新成功")
Write(c, CodeSuccess, gin.H{}, "刷新成功")
}
func (h *API) SetPolicyPaths(c *gin.Context) {
var req dto.SetPolicyPathsRequest
var req service.SetPolicyPathsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if err := h.service.SetPolicyPathsRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *API) PolicyPaths(c *gin.Context) {
var req dto.GetPolicyPathsRequest
var req service.GetPolicyPathsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
paths, err := h.service.PolicyPathResponses(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"paths": paths}, "获取成功")
Write(c, CodeSuccess, gin.H{"paths": paths}, "获取成功")
}
func validateAPIRequest(req *dto.APIRequest) string {
func validateAPIRequest(req *service.APIRequest) string {
switch {
case req.Path == "":
return "Path值不能为空"
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -17,42 +15,42 @@ func NewAPIToken(service *service.TokenService) *APIToken {
}
func (h *APIToken) Create(c *gin.Context) {
var req dto.CreateAPITokenRequest
var req service.CreateAPITokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
token, err := h.service.CreateAPIToken(c.Request.Context(), req.UserID, req.AuthorityID, req.Days, req.Remark)
if err != nil {
httpx.Fail(c, "签发失败: "+err.Error())
Fail(c, "签发失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"token": token}, "签发成功")
Write(c, CodeSuccess, gin.H{"token": token}, "签发成功")
}
func (h *APIToken) List(c *gin.Context) {
var req dto.APITokenListRequest
var req service.APITokenListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.APITokens(c.Request.Context(), req.Page, req.PageSize, req.UserID, req.Status)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *APIToken) Delete(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DisableAPIToken(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "作废失败")
Fail(c, "作废失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "作废成功")
Write(c, CodeSuccess, gin.H{}, "作废成功")
}
@@ -7,9 +7,7 @@ import (
"time"
"kra/app/system/internal/biz"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -26,135 +24,135 @@ func NewAudit(service *service.AuditService, recorder *service.AuditRecorder, lo
}
func (h *Audit) Operations(c *gin.Context) {
var req dto.OperationRecordSearchRequest
var req service.OperationRecordSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.OperationsFilter(c.Request.Context(), req.Page, req.PageSize, req.Path, req.Method, req.Status)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Audit) Operation(c *gin.Context) {
var req dto.AuditIDQuery
var req service.AuditIDQuery
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
item, err := h.service.Operation(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功")
Write(c, CodeSuccess, gin.H{"reSysOperationRecord": item}, "查询成功")
}
func (h *Audit) DeleteOperation(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteOperations(c.Request.Context(), []int{int(req.ID)}); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Audit) DeleteOperations(c *gin.Context) {
var req dto.AuditIDsRequest
var req service.AuditIDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteOperations(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "批量删除失败")
Fail(c, "批量删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Audit) Logins(c *gin.Context) {
var req dto.LoginLogSearchRequest
var req service.LoginLogSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.LoginsFilter(c.Request.Context(), req.Page, req.PageSize, req.Username, req.Status)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Audit) Login(c *gin.Context) {
var req dto.AuditIDQuery
var req service.AuditIDQuery
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.service.Login(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "查询成功")
Write(c, CodeSuccess, item, "查询成功")
}
func (h *Audit) DeleteLogin(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteLogins(c.Request.Context(), []int{int(req.ID)}); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Audit) DeleteLogins(c *gin.Context) {
var req dto.AuditIDsRequest
var req service.AuditIDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteLogins(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "批量删除失败")
Fail(c, "批量删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Audit) DataAccess(c *gin.Context) {
var req dto.DataAccessListRequest
var req service.DataAccessListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.DataAccessRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Audit) DeleteDataAccess(c *gin.Context) {
var req dto.IDsRequest
var req service.IDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteDataAccess(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Audit) LogDates(c *gin.Context) {
@@ -162,7 +160,7 @@ func (h *Audit) LogDates(c *gin.Context) {
// Month uses `binding:"required"`; an omitted or empty query is a
// generic parameter error, not the service's format-specific error.
if month == "" {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
data, err := h.logs.LogDates(c.Request.Context(), month)
@@ -170,12 +168,12 @@ func (h *Audit) LogDates(c *gin.Context) {
failLogViewer(c, err, h.logger)
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
Write(c, CodeSuccess, data, "获取成功")
}
func (h *Audit) LogFiles(c *gin.Context) {
date := c.Query("date")
if date == "" {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
data, err := h.logs.LogFiles(c.Request.Context(), date)
@@ -183,19 +181,19 @@ func (h *Audit) LogFiles(c *gin.Context) {
failLogViewer(c, err, h.logger)
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
Write(c, CodeSuccess, data, "获取成功")
}
func (h *Audit) LogContent(c *gin.Context) {
date, path := c.Query("date"), c.Query("path")
if date == "" || path == "" {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
var cursor *int64
if raw := c.Query("cursor"); raw != "" {
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
cursor = &value
@@ -207,7 +205,7 @@ func (h *Audit) LogContent(c *gin.Context) {
failLogViewer(c, err, h.logger)
return
}
httpx.Write(c, httpx.CodeSuccess, data, "获取成功")
Write(c, CodeSuccess, data, "获取成功")
}
func failLogViewer(c *gin.Context, err error, logger *slog.Logger) {
@@ -229,76 +227,76 @@ func failLogViewer(c *gin.Context, err error, logger *slog.Logger) {
case errors.Is(err, biz.ErrLogRootUnavailable):
message = "日志目录不可读取"
}
httpx.Fail(c, message)
Fail(c, message)
}
func (h *Audit) DeleteError(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := h.service.DeleteErrors(c.Request.Context(), []uint{uint(id)}); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Audit) DeleteErrors(c *gin.Context) {
if err := h.service.DeleteErrors(c.Request.Context(), IDsFromQuery(c)); err != nil {
httpx.Fail(c, "批量删除失败:"+err.Error())
Fail(c, "批量删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Audit) UpdateError(c *gin.Context) {
var req dto.ErrorRecordMutationRequest
var req service.ErrorRecordMutationRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateErrorRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
Fail(c, "更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Audit) Error(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Error(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败:"+err.Error())
Fail(c, "查询失败:"+err.Error())
return
}
httpx.OKWithData(c, item)
OKWithData(c, item)
}
func (h *Audit) Errors(c *gin.Context) {
p, size, err := page(c)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
createdAtRange := make([]time.Time, 0, 2)
for _, raw := range c.QueryArray("createdAtRange[]") {
value, parseErr := time.Parse(time.RFC3339, raw)
if parseErr != nil {
httpx.Fail(c, parseErr.Error())
Fail(c, parseErr.Error())
return
}
createdAtRange = append(createdAtRange, value)
}
items, total, err := h.service.ErrorsFilter(c.Request.Context(), p, size, c.Query("form"), c.Query("info"), createdAtRange)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Audit) CreateError(c *gin.Context) {
var req dto.ErrorRecordMutationRequest
var req service.ErrorRecordMutationRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.recorder.CreateErrorMutationRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -15,155 +13,155 @@ func NewAuthority(service *service.AuthorityService) *Authority { return &Author
func (h *Authority) List(c *gin.Context) {
items, err := h.service.Authorities(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
Write(c, CodeSuccess, items, "获取成功")
}
func (h *Authority) Create(c *gin.Context) {
var req dto.AuthorityRequest
var req service.AuthorityRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if req.AuthorityName == "" {
httpx.Fail(c, "AuthorityName值不能为空")
Fail(c, "AuthorityName值不能为空")
return
}
value, err := h.service.CreateAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败"+err.Error())
Fail(c, "创建失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "创建成功")
Write(c, CodeSuccess, gin.H{"authority": value}, "创建成功")
}
func (h *Authority) Copy(c *gin.Context) {
var req dto.CopyAuthorityRequest
var req service.CopyAuthorityRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.OldAuthorityID == 0 {
httpx.Fail(c, "OldAuthorityId值不能为空")
Fail(c, "OldAuthorityId值不能为空")
return
}
if req.Authority.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if req.Authority.AuthorityName == "" {
httpx.Fail(c, "AuthorityName值不能为空")
Fail(c, "AuthorityName值不能为空")
return
}
value, err := h.service.CopyAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "拷贝失败"+err.Error())
Fail(c, "拷贝失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "拷贝成功")
Write(c, CodeSuccess, gin.H{"authority": value}, "拷贝成功")
}
func (h *Authority) Update(c *gin.Context) {
var req dto.AuthorityRequest
var req service.AuthorityRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if req.AuthorityName == "" {
httpx.Fail(c, "AuthorityName值不能为空")
Fail(c, "AuthorityName值不能为空")
return
}
value, err := h.service.UpdateAuthorityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "更新失败"+err.Error())
Fail(c, "更新失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authority": value}, "更新成功")
Write(c, CodeSuccess, gin.H{"authority": value}, "更新成功")
}
func (h *Authority) Delete(c *gin.Context) {
var req dto.DeleteAuthorityRequest
var req service.DeleteAuthorityRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if err := h.service.DeleteAuthority(c.Request.Context(), req.AuthorityID); err != nil {
httpx.Fail(c, "删除失败"+err.Error())
Fail(c, "删除失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Authority) SetUsers(c *gin.Context) {
var req dto.SetRoleUsersRequest
var req service.SetRoleUsersRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "角色ID不能为空")
Fail(c, "角色ID不能为空")
return
}
if err := h.service.SetAuthorityUsers(c.Request.Context(), req.AuthorityID, req.UserIDs); err != nil {
httpx.Fail(c, "设置失败"+err.Error())
Fail(c, "设置失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *Authority) Users(c *gin.Context) {
var req dto.SetRoleUsersRequest
var req service.SetRoleUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
ids, err := h.service.AuthorityUserIDs(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
if ids == nil {
ids = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
Write(c, CodeSuccess, ids, "获取成功")
}
func (h *Authority) SetDataScope(c *gin.Context) {
var req dto.SetDataScopeRequest
var req service.SetDataScopeRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "角色ID不能为空")
Fail(c, "角色ID不能为空")
return
}
if err := h.service.SetDataScope(c.Request.Context(), req.AuthorityID, req.DataScope, req.DepartmentIDs); err != nil {
httpx.Fail(c, "设置失败"+err.Error())
Fail(c, "设置失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *Authority) DataScopeDepartments(c *gin.Context) {
var req dto.SetDataScopeRequest
var req service.SetDataScopeRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
ids, err := h.service.DataScopeDepartmentIDs(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
if ids == nil {
ids = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
Write(c, CodeSuccess, ids, "获取成功")
}
@@ -1,11 +1,8 @@
package handler
import (
"strconv"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"strconv"
"github.com/gin-gonic/gin"
)
@@ -17,152 +14,152 @@ func NewDictionary(service *service.DictionaryService) *Dictionary {
}
func (h *Dictionary) Create(c *gin.Context) {
var req dto.DictionaryRequest
var req service.DictionaryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
created, err := h.service.CreateDictionaryRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败")
Fail(c, "创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, created, "创建成功")
Write(c, CodeSuccess, created, "创建成功")
}
func (h *Dictionary) Update(c *gin.Context) {
var req dto.DictionaryRequest
var req service.DictionaryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateDictionaryRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Dictionary) Delete(c *gin.Context) {
var req dto.DictionaryRequest
var req service.DictionaryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteDictionary(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Dictionary) Find(c *gin.Context) {
var req dto.DictionaryRequest
var req service.DictionaryRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.service.Dictionary(c.Request.Context(), req.ID, req.Type, req.Status, true)
if err != nil {
httpx.Fail(c, "字典未创建或未开启")
Fail(c, "字典未创建或未开启")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"resysDictionary": item}, "查询成功")
Write(c, CodeSuccess, gin.H{"resysDictionary": item}, "查询成功")
}
func (h *Dictionary) List(details bool) gin.HandlerFunc {
return func(c *gin.Context) {
items, _, err := h.service.Dictionaries(c.Request.Context(), 0, 0, c.Query("name"), "", details)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
Write(c, CodeSuccess, items, "获取成功")
}
}
func (h *Dictionary) Export(c *gin.Context) {
var req dto.DictionaryRequest
var req service.DictionaryRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "字典ID不能为空")
Fail(c, "字典ID不能为空")
return
}
item, err := h.service.ExportDictionary(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "导出失败")
Fail(c, "导出失败")
return
}
httpx.Write(c, httpx.CodeSuccess, item, "导出成功")
Write(c, CodeSuccess, item, "导出成功")
}
func (h *Dictionary) Import(c *gin.Context) {
var req dto.ImportDictionaryRequest
var req service.ImportDictionaryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.ImportDictionaryJSON(c.Request.Context(), req.JSON); err != nil {
httpx.Fail(c, "导入失败: "+err.Error())
Fail(c, "导入失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功")
Write(c, CodeSuccess, gin.H{}, "导入成功")
}
func (h *Dictionary) CreateDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
var req service.DictionaryDetailRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.CreateDictionaryDetailRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
Fail(c, "创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Dictionary) UpdateDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
var req service.DictionaryDetailRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateDictionaryDetailRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Dictionary) DeleteDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
var req service.DictionaryDetailRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.DeleteDictionaryDetail(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Dictionary) FindDetail(c *gin.Context) {
var req dto.DictionaryDetailRequest
var req service.DictionaryDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
item, err := h.service.DictionaryDetail(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功")
Write(c, CodeSuccess, gin.H{"reSysDictionaryDetail": item}, "查询成功")
}
func (h *Dictionary) Details(c *gin.Context) {
p, size, err := page(c)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
var dictionaryID uint64
@@ -170,15 +167,15 @@ func (h *Dictionary) Details(c *gin.Context) {
var parseErr error
dictionaryID, parseErr = strconv.ParseUint(raw, 10, 0)
if parseErr != nil {
httpx.Fail(c, parseErr.Error())
Fail(c, parseErr.Error())
return
}
}
filter := dto.DictionaryDetailListRequest{DictionaryID: uint(dictionaryID), Label: c.Query("label"), Value: c.Query("value")}
filter := service.DictionaryDetailListRequest{DictionaryID: uint(dictionaryID), Label: c.Query("label"), Value: c.Query("value")}
if raw, exists := c.GetQuery("status"); exists {
value, parseErr := strconv.ParseBool(raw)
if parseErr != nil {
httpx.Fail(c, parseErr.Error())
Fail(c, parseErr.Error())
return
}
filter.Status = &value
@@ -186,7 +183,7 @@ func (h *Dictionary) Details(c *gin.Context) {
if raw, exists := c.GetQuery("parentID"); exists {
value, parseErr := strconv.ParseUint(raw, 10, 64)
if parseErr != nil {
httpx.Fail(c, parseErr.Error())
Fail(c, parseErr.Error())
return
}
parentID := uint(value)
@@ -195,17 +192,17 @@ func (h *Dictionary) Details(c *gin.Context) {
if raw, exists := c.GetQuery("level"); exists {
value, parseErr := strconv.Atoi(raw)
if parseErr != nil {
httpx.Fail(c, parseErr.Error())
Fail(c, parseErr.Error())
return
}
filter.Level = &value
}
items, total, err := h.service.DictionaryDetailsRequest(c.Request.Context(), p, size, filter)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Dictionary) Tree(byType bool) gin.HandlerFunc {
return func(c *gin.Context) {
@@ -215,52 +212,52 @@ func (h *Dictionary) Tree(byType bool) gin.HandlerFunc {
if byType {
typ = c.Query("type")
if typ == "" {
httpx.Fail(c, "字典类型不能为空")
Fail(c, "字典类型不能为空")
return
}
} else if rawID == "" {
httpx.Fail(c, "字典ID不能为空")
Fail(c, "字典ID不能为空")
return
} else if parseErr != nil {
httpx.Fail(c, "字典ID格式错误")
Fail(c, "字典ID格式错误")
return
}
items, err := h.service.DictionaryTree(c.Request.Context(), uint(id), typ)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功")
Write(c, CodeSuccess, gin.H{"list": items}, "获取成功")
}
}
func (h *Dictionary) DetailsByParent(c *gin.Context) {
var req dto.DictionaryDetailsByParentRequest
var req service.DictionaryDetailsByParentRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, err := h.service.DictionaryDetailsByParent(c.Request.Context(), req.DictionaryID, req.ParentID, req.IncludeChildren)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items}, "获取成功")
Write(c, CodeSuccess, gin.H{"list": items}, "获取成功")
}
func (h *Dictionary) Path(c *gin.Context) {
rawID := c.Query("id")
if rawID == "" {
httpx.Fail(c, "字典详情ID不能为空")
Fail(c, "字典详情ID不能为空")
return
}
id, parseErr := strconv.ParseUint(rawID, 10, 32)
if parseErr != nil {
httpx.Fail(c, "字典详情ID格式错误")
Fail(c, "字典详情ID格式错误")
return
}
path, err := h.service.DictionaryPath(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"path": path}, "获取成功")
Write(c, CodeSuccess, gin.H{"path": path}, "获取成功")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -14,21 +12,21 @@ func NewEmail(service *service.EmailService) *Email { return &Email{service: ser
func (h *Email) Test(c *gin.Context) {
if err := h.service.Test(c.Request.Context()); err != nil {
httpx.Fail(c, "发送失败")
Fail(c, "发送失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
Write(c, CodeSuccess, gin.H{}, "发送成功")
}
func (h *Email) Send(c *gin.Context) {
var req dto.SendEmailRequest
var req service.SendEmailRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Send(c.Request.Context(), req.To, req.Subject, req.Body); err != nil {
httpx.Fail(c, "发送失败")
Fail(c, "发送失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "发送成功")
Write(c, CodeSuccess, gin.H{}, "发送成功")
}
@@ -2,14 +2,11 @@ package handler
import (
"errors"
"kra/app/system/internal/service"
"net/http"
"net/url"
"strings"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
@@ -45,179 +42,179 @@ func exportIssueParams(values url.Values, blank bool) (map[string]string, error)
}
func (h *Export) Create(c *gin.Context) {
var req dto.ExportTemplateRequest
var req service.ExportTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Name == "" {
httpx.Fail(c, "Name值不能为空")
Fail(c, "Name值不能为空")
return
}
if err := h.service.CreateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败")
Fail(c, "创建失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Export) Update(c *gin.Context) {
var req dto.ExportTemplateRequest
var req service.ExportTemplateRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Name == "" {
httpx.Fail(c, "Name值不能为空")
Fail(c, "Name值不能为空")
return
}
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Export) Delete(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
// Deleting an empty model is rejected by GORM's global-update guard in the
// reference endpoint. Preserve the same externally visible failure.
if req.ID == 0 {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
if err := h.service.Delete(c.Request.Context(), []uint{req.ID}); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Export) DeleteMany(c *gin.Context) {
var req dto.IDsRequest
var req service.IDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Delete(c.Request.Context(), req.IDs); err != nil {
httpx.Fail(c, "批量删除失败")
Fail(c, "批量删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Export) Find(c *gin.Context) {
var req dto.ExportTemplateRequest
var req service.ExportTemplateRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.service.Template(c.Request.Context(), req.ID, "")
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.OKWithData(c, gin.H{"resysExportTemplate": item})
OKWithData(c, gin.H{"resysExportTemplate": item})
}
func (h *Export) List(c *gin.Context) {
p, size, err := page(c)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
start, startErr := parseTime(c.Query("startCreatedAt"))
end, endErr := parseTime(c.Query("endCreatedAt"))
if startErr != nil || endErr != nil {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
items, total, err := h.service.TemplatesFilter(c.Request.Context(), p, size, c.Query("name"), c.Query("tableName"), c.Query("templateID"), start, end)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: p, PageSize: size}, "获取成功")
}
func (h *Export) Preview(c *gin.Context) {
templateID := c.Query("templateID")
if templateID == "" {
httpx.Fail(c, "模板ID不能为空")
Fail(c, "模板ID不能为空")
return
}
params, _ := exportParams(c.Request.URL.Query())
sql, err := h.service.Preview(c.Request.Context(), templateID, params)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.OKWithData(c, gin.H{"sql": sql})
OKWithData(c, gin.H{"sql": sql})
}
func (h *Export) Issue(blank bool) gin.HandlerFunc {
return func(c *gin.Context) {
templateID := c.Query("templateID")
if templateID == "" {
httpx.Fail(c, "模板ID不能为空")
Fail(c, "模板ID不能为空")
return
}
params, err := exportIssueParams(c.Request.URL.Query(), blank)
if err != nil {
httpx.Fail(c, "解析 params 参数失败")
Fail(c, "解析 params 参数失败")
return
}
token, err := h.service.IssueToken(c.Request.Context(), templateID, params, blank)
if err != nil {
httpx.Fail(c, "导出令牌创建失败")
Fail(c, "导出令牌创建失败")
return
}
path := "/sysExportTemplate/exportExcelByToken?token=" + token
if blank {
path = "/sysExportTemplate/exportTemplateByToken?token=" + token
}
httpx.OKWithData(c, path)
OKWithData(c, path)
}
}
func (h *Export) Import(c *gin.Context) {
templateID := c.Query("templateID")
if templateID == "" {
httpx.Fail(c, "模板ID不能为空")
Fail(c, "模板ID不能为空")
return
}
file, err := c.FormFile("file")
if err != nil {
httpx.Fail(c, "文件获取失败")
Fail(c, "文件获取失败")
return
}
opened, err := file.Open()
if err != nil {
httpx.Fail(c, "读取文件失败")
Fail(c, "读取文件失败")
return
}
defer opened.Close()
if err = h.service.Import(c.Request.Context(), templateID, opened); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功")
Write(c, CodeSuccess, gin.H{}, "导入成功")
}
func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Query("token")
if token == "" {
httpx.Fail(c, "导出token不能为空")
Fail(c, "导出token不能为空")
return
}
value, err := h.service.ConsumeToken(c.Request.Context(), token, expectBlank)
if err != nil {
switch {
case errors.Is(err, service.ErrExportTokenMalformed):
httpx.Fail(c, "解析导出参数失败")
Fail(c, "解析导出参数失败")
case errors.Is(err, service.ErrExportTokenType):
httpx.Fail(c, "token类型错误")
Fail(c, "token类型错误")
default:
httpx.Fail(c, "导出token无效或已过期")
Fail(c, "导出token无效或已过期")
}
return
}
@@ -229,7 +226,7 @@ func (h *Export) Download(expectBlank bool) gin.HandlerFunc {
data, name, err = h.service.Export(c.Request.Context(), value.TemplateID, value.Params)
}
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
if expectBlank {
@@ -0,0 +1,25 @@
package handler
import (
"github.com/gin-gonic/gin"
"kra/app/system/internal/biz"
"kra/app/system/internal/server/httpx"
"kra/app/system/internal/server/middleware"
)
const (
CodeSuccess = httpx.CodeSuccess
CodeError = httpx.CodeError
CodePasswordChangeRequired = httpx.CodePasswordChangeRequired
)
type Response = httpx.Response
type PageResult = httpx.PageResult
func Write(c *gin.Context, code int, data any, message string) { httpx.Write(c, code, data, message) }
func OK(c *gin.Context) { httpx.OK(c) }
func OKWithData(c *gin.Context, data any) { httpx.OKWithData(c, data) }
func Fail(c *gin.Context, message string) { httpx.Fail(c, message) }
func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) }
func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) }
func Claims(c *gin.Context) *biz.AuthClaims { return middleware.Claims(c) }
@@ -2,14 +2,10 @@ package handler
import (
"errors"
"kra/app/system/internal/service"
"net/http"
"strconv"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
servermiddleware "kra/app/system/internal/transport/middleware"
"github.com/gin-gonic/gin"
)
@@ -28,14 +24,14 @@ func rejectMediaTooLarge(c *gin.Context, err error, message string) bool {
if !errors.As(err, &tooLarge) {
return false
}
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: message})
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: message})
return true
}
func (h *Media) Upload(c *gin.Context) {
claims := servermiddleware.Claims(c)
claims := Claims(c)
if claims == nil {
httpx.Fail(c, "接收文件失败")
Fail(c, "接收文件失败")
return
}
limit := h.limitMultipartBody(c)
@@ -44,18 +40,18 @@ func (h *Media) Upload(c *gin.Context) {
if rejectMediaTooLarge(c, err, "文件超过大小上限") {
return
}
httpx.Fail(c, "接收文件失败")
Fail(c, "接收文件失败")
return
}
if header.Size > limit {
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: "文件超过大小上限"})
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "文件超过大小上限"})
return
}
opened, err := header.Open()
if err != nil {
// Let the upload service surface this as the generic upload
// failure; keep the endpoint message consistent.
httpx.Fail(c, "上传文件失败")
Fail(c, "上传文件失败")
return
}
defer opened.Close()
@@ -63,41 +59,41 @@ func (h *Media) Upload(c *gin.Context) {
save := c.DefaultQuery("noSave", "0") == "0"
item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
if err != nil {
httpx.Fail(c, "上传文件失败")
Fail(c, "上传文件失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"file": item}, "上传成功")
Write(c, CodeSuccess, gin.H{"file": item}, "上传成功")
}
func (h *Media) List(c *gin.Context) {
var req dto.MediaListRequest
var req service.MediaListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.MediaListRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Media) Delete(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Media) DeleteMany(c *gin.Context) {
var req dto.MediaBatchDeleteRequest
var req service.MediaBatchDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
failed := []uint{}
@@ -107,121 +103,121 @@ func (h *Media) DeleteMany(c *gin.Context) {
}
}
if len(failed) > 0 {
httpx.Write(c, httpx.CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
Write(c, CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Media) Find(c *gin.Context) {
id, parseErr := strconv.ParseUint(c.Query("id"), 10, 64)
if parseErr != nil {
httpx.Fail(c, "文件ID非法")
Fail(c, "文件ID非法")
return
}
item, err := h.service.Media(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.OKWithData(c, item)
OKWithData(c, item)
}
func (h *Media) Rename(c *gin.Context) {
var req dto.RenameMediaRequest
var req service.RenameMediaRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Rename(c.Request.Context(), req.ID, req.Name); err != nil {
httpx.Fail(c, "编辑失败")
Fail(c, "编辑失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "编辑成功")
Write(c, CodeSuccess, gin.H{}, "编辑成功")
}
func (h *Media) ImportURLs(c *gin.Context) {
var req []dto.ImportMediaRequest
var req []service.ImportMediaRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.ImportURLRequests(c.Request.Context(), req); err != nil {
httpx.Fail(c, "导入URL失败")
Fail(c, "导入URL失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入URL成功")
Write(c, CodeSuccess, gin.H{}, "导入URL成功")
}
func (h *Media) Storage(c *gin.Context) {
var req dto.StorageListRequest
var req service.StorageListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, next, more, err := h.service.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit)
if err != nil {
httpx.Fail(c, "列举存储桶文件失败")
Fail(c, "列举存储桶文件失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功")
Write(c, CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功")
}
func (h *Media) Categories(c *gin.Context) {
items, err := h.service.Categories(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取分类列表失败")
Fail(c, "获取分类列表失败")
return
}
httpx.OKWithData(c, items)
OKWithData(c, items)
}
func (h *Media) SaveCategory(c *gin.Context) {
var req dto.CategoryRequest
var req service.CategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
if err := h.service.SaveCategoryRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建/更新失败:"+err.Error())
Fail(c, "创建/更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建/更新成功")
Write(c, CodeSuccess, gin.H{}, "创建/更新成功")
}
func (h *Media) DeleteCategory(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
if err := h.service.DeleteCategory(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Media) InitUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
var req dto.InitUploadRequest
claims := Claims(c)
var req service.InitUploadRequest
if claims == nil {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if config := h.service.MediaConfig(); config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize {
httpx.Fail(c, "文件超过大小上限")
Fail(c, "文件超过大小上限")
return
}
result, err := h.service.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, result, "成功")
Write(c, CodeSuccess, result, "成功")
}
func (h *Media) SaveChunk(c *gin.Context) {
claims := servermiddleware.Claims(c)
claims := Claims(c)
if claims == nil {
httpx.Fail(c, "接收分片失败")
Fail(c, "接收分片失败")
return
}
limit := h.limitMultipartBody(c)
@@ -230,63 +226,63 @@ func (h *Media) SaveChunk(c *gin.Context) {
if rejectMediaTooLarge(c, err, "分片超过大小上限") {
return
}
httpx.Fail(c, "接收分片失败")
Fail(c, "接收分片失败")
return
}
if header.Size > limit {
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, httpx.Response{Code: httpx.CodeError, Data: gin.H{}, Msg: "分片超过大小上限"})
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "分片超过大小上限"})
return
}
uploadID, parseErr := strconv.ParseUint(c.PostForm("uploadId"), 10, 64)
if parseErr != nil || uploadID == 0 {
httpx.Fail(c, "上传会话 ID 非法")
Fail(c, "上传会话 ID 非法")
return
}
index, parseErr := strconv.Atoi(c.PostForm("chunkIndex"))
if parseErr != nil || index < 0 {
httpx.Fail(c, "分片序号非法")
Fail(c, "分片序号非法")
return
}
opened, err := header.Open()
if err != nil {
httpx.Fail(c, "分片读取失败")
Fail(c, "分片读取失败")
return
}
defer opened.Close()
if err = h.service.SaveChunk(c.Request.Context(), claims.ID, uint(uploadID), index, c.PostForm("chunkHash"), opened); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "ok")
Write(c, CodeSuccess, gin.H{}, "ok")
}
func (h *Media) CompleteUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
var req dto.CompleteUploadRequest
claims := Claims(c)
var req service.CompleteUploadRequest
if claims == nil {
httpx.Fail(c, "参数错误")
Fail(c, "参数错误")
return
}
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.service.CompleteUpload(c.Request.Context(), claims.ID, req.UploadID)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"media": item}, "成功")
Write(c, CodeSuccess, gin.H{"media": item}, "成功")
}
func (h *Media) CancelUpload(c *gin.Context) {
claims := servermiddleware.Claims(c)
claims := Claims(c)
id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64)
if claims == nil {
httpx.Fail(c, "未登录")
Fail(c, "未登录")
return
}
if err := h.service.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已取消")
Write(c, CodeSuccess, gin.H{}, "已取消")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -15,170 +13,170 @@ func NewMenu(service *service.MenuService) *Menu { return &Menu{service: service
func (h *Menu) List(c *gin.Context) {
items, err := h.service.Tree(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
Write(c, CodeSuccess, items, "获取成功")
}
func (h *Menu) Tree(c *gin.Context) {
items, err := h.service.Tree(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": items}, "获取成功")
Write(c, CodeSuccess, gin.H{"menus": items}, "获取成功")
}
func (h *Menu) Create(c *gin.Context) {
var req dto.MenuRequest
var req service.MenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Path == "" {
httpx.Fail(c, "Path值不能为空")
Fail(c, "Path值不能为空")
return
}
if req.Name == "" {
httpx.Fail(c, "Name值不能为空")
Fail(c, "Name值不能为空")
return
}
if req.Component == "" {
httpx.Fail(c, "Component值不能为空")
Fail(c, "Component值不能为空")
return
}
if req.Sort < 0 {
httpx.Fail(c, "Sort长度或值不在合法范围,ge=0")
Fail(c, "Sort长度或值不在合法范围,ge=0")
return
}
if req.Meta.Title == "" {
httpx.Fail(c, "Title值不能为空")
Fail(c, "Title值不能为空")
return
}
if err := h.service.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "添加失败:"+err.Error())
Fail(c, "添加失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功")
Write(c, CodeSuccess, gin.H{}, "添加成功")
}
func (h *Menu) Update(c *gin.Context) {
var req dto.MenuRequest
var req service.MenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Path == "" {
httpx.Fail(c, "Path值不能为空")
Fail(c, "Path值不能为空")
return
}
if req.Name == "" {
httpx.Fail(c, "Name值不能为空")
Fail(c, "Name值不能为空")
return
}
if req.Component == "" {
httpx.Fail(c, "Component值不能为空")
Fail(c, "Component值不能为空")
return
}
if req.Sort < 0 {
httpx.Fail(c, "Sort长度或值不在合法范围,ge=0")
Fail(c, "Sort长度或值不在合法范围,ge=0")
return
}
if req.Meta.Title == "" {
httpx.Fail(c, "Title值不能为空")
Fail(c, "Title值不能为空")
return
}
if err := h.service.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败")
Fail(c, "更新失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Menu) Delete(c *gin.Context) {
var req dto.DeleteMenuRequest
var req service.DeleteMenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
if err := h.service.Delete(c.Request.Context(), uint(req.ID)); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Menu) Find(c *gin.Context) {
var req dto.GetMenuRequest
var req service.GetMenuRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
item, err := h.service.Find(c.Request.Context(), uint(req.ID))
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menu": item}, "获取成功")
Write(c, CodeSuccess, gin.H{"menu": item}, "获取成功")
}
func (h *Menu) SetAuthorityMenus(c *gin.Context) {
var req dto.SetAuthorityMenusRequest
var req service.SetAuthorityMenusRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
if err := h.service.SetAuthorityMenus(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "添加失败")
Fail(c, "添加失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "添加成功")
Write(c, CodeSuccess, gin.H{}, "添加成功")
}
func (h *Menu) AuthorityMenus(c *gin.Context) {
var req dto.GetAuthorityMenusRequest
var req service.GetAuthorityMenusRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
menus, err := h.service.AuthorityMenus(c.Request.Context(), req.AuthorityID)
if err != nil {
httpx.Write(c, httpx.CodeError, gin.H{"menus": menus}, "获取失败")
Write(c, CodeError, gin.H{"menus": menus}, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功")
Write(c, CodeSuccess, gin.H{"menus": menus}, "获取成功")
}
func (h *Menu) RoleIDs(c *gin.Context) {
var req dto.SetMenuRolesRequest
var req service.SetMenuRolesRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.MenuID == 0 {
httpx.Fail(c, "菜单ID不能为空")
Fail(c, "菜单ID不能为空")
return
}
ids, err := h.service.RoleIDs(c.Request.Context(), req.MenuID)
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
if ids == nil {
@@ -186,28 +184,28 @@ func (h *Menu) RoleIDs(c *gin.Context) {
}
defaultIDs, err := h.service.DefaultRouterRoleIDs(c.Request.Context(), req.MenuID)
if err != nil {
httpx.Fail(c, "获取失败"+err.Error())
Fail(c, "获取失败"+err.Error())
return
}
if defaultIDs == nil {
defaultIDs = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"authorityIds": ids, "defaultRouterAuthorityIds": defaultIDs}, "获取成功")
Write(c, CodeSuccess, gin.H{"authorityIds": ids, "defaultRouterAuthorityIds": defaultIDs}, "获取成功")
}
func (h *Menu) SetRoles(c *gin.Context) {
var req dto.SetMenuRolesRequest
var req service.SetMenuRolesRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.MenuID == 0 {
httpx.Fail(c, "菜单ID不能为空")
Fail(c, "菜单ID不能为空")
return
}
if err := h.service.SetRoles(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败"+err.Error())
Fail(c, "设置失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
@@ -2,8 +2,6 @@ package handler
import (
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"kra/app/system/internal/transport/middleware"
"github.com/gin-gonic/gin"
)
@@ -12,15 +10,15 @@ type Navigation struct{ service *service.UserService }
func NewNavigation(service *service.UserService) *Navigation { return &Navigation{service: service} }
func (h *Navigation) Menu(c *gin.Context) {
claims := middleware.Claims(c)
claims := Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
menus, err := h.service.Menus(c.Request.Context(), claims.AuthorityID)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"menus": menus}, "获取成功")
Write(c, CodeSuccess, gin.H{"menus": menus}, "获取成功")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -18,236 +16,236 @@ func NewOrganization(departments *service.DepartmentService, positions *service.
}
func (h *Organization) ListDepartments(c *gin.Context) {
var req dto.DepartmentListRequest
var req service.DepartmentListRequest
_ = c.ShouldBindJSON(&req)
items, err := h.departments.Departments(c.Request.Context(), req.Name)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
Write(c, CodeSuccess, items, "获取成功")
}
func (h *Organization) CreateDepartment(c *gin.Context) {
var req dto.DepartmentRequest
var req service.DepartmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Name == "" {
httpx.Fail(c, "部门名称不能为空")
Fail(c, "部门名称不能为空")
return
}
if err := h.departments.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Organization) UpdateDepartment(c *gin.Context) {
var req dto.DepartmentRequest
var req service.DepartmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "部门ID不能为空")
Fail(c, "部门ID不能为空")
return
}
if err := h.departments.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
Fail(c, "更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Organization) DeleteDepartment(c *gin.Context) {
var req dto.DeleteDepartmentRequest
var req service.DeleteDepartmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Organization) FindDepartment(c *gin.Context) {
var req dto.DeleteDepartmentRequest
var req service.DeleteDepartmentRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.departments.Department(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
Write(c, CodeSuccess, item, "获取成功")
}
func (h *Organization) DepartmentUsers(c *gin.Context) {
var req dto.SetDepartmentUsersRequest
var req service.SetDepartmentUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
ids, err := h.departments.UserIDs(c.Request.Context(), req.DepartmentID)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
if ids == nil {
ids = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
Write(c, CodeSuccess, ids, "获取成功")
}
func (h *Organization) SetDepartmentUsers(c *gin.Context) {
var req dto.SetDepartmentUsersRequest
var req service.SetDepartmentUsersRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.DepartmentID == 0 {
httpx.Fail(c, "部门ID不能为空")
Fail(c, "部门ID不能为空")
return
}
if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
Fail(c, "设置失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *Organization) SetUserDepartments(c *gin.Context) {
var req dto.SetUserDepartmentsRequest
var req service.SetUserDepartmentsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "用户ID不能为空")
Fail(c, "用户ID不能为空")
return
}
if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
Fail(c, "设置失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *Organization) ListPositions(c *gin.Context) {
var req dto.PositionListRequest
var req service.PositionListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.positions.Positions(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Organization) CreatePosition(c *gin.Context) {
var req dto.PositionRequest
var req service.PositionRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Name == "" {
httpx.Fail(c, "岗位名称不能为空")
Fail(c, "岗位名称不能为空")
return
}
if err := h.positions.Create(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Organization) UpdatePosition(c *gin.Context) {
var req dto.PositionRequest
var req service.PositionRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "岗位ID不能为空")
Fail(c, "岗位ID不能为空")
return
}
if err := h.positions.Update(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
Fail(c, "更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Organization) DeletePosition(c *gin.Context) {
var req dto.DeletePositionRequest
var req service.DeletePositionRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Organization) FindPosition(c *gin.Context) {
var req dto.DeletePositionRequest
var req service.DeletePositionRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
item, err := h.positions.Position(c.Request.Context(), req.ID)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
Write(c, CodeSuccess, item, "获取成功")
}
func (h *Organization) PositionUsers(c *gin.Context) {
var req dto.SetPositionUsersRequest
var req service.SetPositionUsersRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
ids, err := h.positions.UserIDs(c.Request.Context(), req.PositionID)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
if ids == nil {
ids = []uint{}
}
httpx.Write(c, httpx.CodeSuccess, ids, "获取成功")
Write(c, CodeSuccess, ids, "获取成功")
}
func (h *Organization) SetPositionUsers(c *gin.Context) {
var req dto.SetPositionUsersRequest
var req service.SetPositionUsersRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.PositionID == 0 {
httpx.Fail(c, "岗位ID不能为空")
Fail(c, "岗位ID不能为空")
return
}
if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
Fail(c, "设置失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *Organization) SetUserPositions(c *gin.Context) {
var req dto.SetUserPositionsRequest
var req service.SetUserPositionsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "用户ID不能为空")
Fail(c, "用户ID不能为空")
return
}
if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败:"+err.Error())
Fail(c, "设置失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -17,76 +15,76 @@ func NewParameter(service *service.ParameterService) *Parameter {
}
func (h *Parameter) Create(c *gin.Context) {
var req dto.SystemParameterRequest
var req service.SystemParameterRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.CreateParameterRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "创建失败:"+err.Error())
Fail(c, "创建失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Parameter) Update(c *gin.Context) {
var req dto.SystemParameterRequest
var req service.SystemParameterRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateParameterRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败:"+err.Error())
Fail(c, "更新失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Parameter) Delete(c *gin.Context) {
if err := h.service.DeleteParameters(c.Request.Context(), []string{c.Query("ID")}); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Parameter) DeleteMany(c *gin.Context) {
ids := c.QueryArray("IDs[]")
if err := h.service.DeleteParameters(c.Request.Context(), ids); err != nil {
httpx.Fail(c, "批量删除失败:"+err.Error())
Fail(c, "批量删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Parameter) Find(c *gin.Context) {
item, err := h.service.ParameterByID(c.Request.Context(), c.Query("ID"))
if err != nil {
httpx.Fail(c, "查询失败:"+err.Error())
Fail(c, "查询失败:"+err.Error())
return
}
httpx.OKWithData(c, item)
OKWithData(c, item)
}
func (h *Parameter) Get(c *gin.Context) {
item, err := h.service.ParameterByKey(c.Request.Context(), c.Query("key"))
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
Write(c, CodeSuccess, item, "获取成功")
}
func (h *Parameter) List(c *gin.Context) {
var req dto.SystemParameterSearchRequest
var req service.SystemParameterSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.ParametersFilter(c.Request.Context(), req.Page, req.PageSize, req.Name, req.Key, req.StartCreatedAt, req.EndCreatedAt)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
@@ -2,11 +2,8 @@ package handler
import (
"io"
"net/http"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"net/http"
"github.com/gin-gonic/gin"
)
@@ -17,89 +14,141 @@ func NewPayment(service *service.PaymentService) *Payment { return &Payment{serv
func (h *Payment) Configs(c *gin.Context) {
v, err := h.service.Configs(c.Request.Context())
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OKWithData(c, v)
OKWithData(c, v)
}
func (h *Payment) SaveConfig(c *gin.Context) {
var req dto.PaymentConfigRequest
var req service.PaymentConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.SaveConfig(c.Request.Context(), &req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OK(c)
OK(c)
}
func (h *Payment) Order(c *gin.Context) {
var req dto.PaymentQueryRequest
var req service.PaymentQueryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
order, err := h.service.Order(c.Request.Context(), req.Provider, req.TradeNo)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OKWithData(c, order)
OKWithData(c, order)
}
func (h *Payment) OrderByPath(c *gin.Context) {
order, err := h.service.Order(c.Request.Context(), c.Param("provider"), c.Param("tradeNo"))
if err != nil {
Fail(c, err.Error())
return
}
OKWithData(c, order)
}
func (h *Payment) RefundByPath(c *gin.Context) {
var req struct {
Amount int64 `json:"amount"`
}
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, err.Error())
return
}
result, err := h.service.Refund(c.Request.Context(), &service.PaymentRefundRequest{
Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"), Amount: req.Amount,
})
if err != nil {
Fail(c, err.Error())
return
}
OKWithData(c, result)
}
func (h *Payment) FulfillByPath(c *gin.Context) {
result, err := h.service.Fulfill(c.Request.Context(), &service.PaymentFulfillRequest{
Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"),
})
if err != nil {
Fail(c, err.Error())
return
}
OKWithData(c, result)
}
func (h *Payment) Orders(c *gin.Context) {
var req dto.PaymentOrderListRequest
var req service.PaymentOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, page, pageSize, err := h.service.Orders(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功")
}
func (h *Payment) Create(c *gin.Context) {
var req dto.PaymentRequest
var req service.PaymentRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
req.ClientIP = c.ClientIP()
result, err := h.service.Create(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OKWithData(c, result)
OKWithData(c, result)
}
func (h *Payment) Query(c *gin.Context) {
var req dto.PaymentQueryRequest
var req service.PaymentQueryRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
result, err := h.service.Query(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OKWithData(c, result)
OKWithData(c, result)
}
func (h *Payment) Refund(c *gin.Context) {
var req dto.PaymentRefundRequest
var req service.PaymentRefundRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
result, err := h.service.Refund(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.OKWithData(c, result)
OKWithData(c, result)
}
func (h *Payment) Fulfill(c *gin.Context) {
var req service.PaymentFulfillRequest
if err := c.ShouldBindJSON(&req); err != nil {
Fail(c, err.Error())
return
}
result, err := h.service.Fulfill(c.Request.Context(), &req)
if err != nil {
Fail(c, err.Error())
return
}
OKWithData(c, result)
}
func (h *Payment) Callback(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8<<20)
@@ -120,12 +169,12 @@ func (h *Payment) Callback(c *gin.Context) {
query[k] = v[0]
}
}
ack, err := h.service.Callback(c.Request.Context(), &dto.PaymentCallbackRequest{Provider: c.Param("provider"), Headers: headers, Body: raw, Query: query})
ack, err := h.service.Callback(c.Request.Context(), &service.PaymentCallbackRequest{Provider: c.Param("provider"), Headers: headers, Body: raw, Query: query})
writePaymentCallbackAck(c, ack)
_ = err
}
func writePaymentCallbackAck(c *gin.Context, ack dto.PaymentCallbackAck) {
func writePaymentCallbackAck(c *gin.Context, ack service.PaymentCallbackAck) {
if ack.StatusCode == 0 {
ack.StatusCode = http.StatusInternalServerError
}
@@ -5,14 +5,14 @@ import (
"testing"
"github.com/gin-gonic/gin"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
)
func TestWritePaymentCallbackAckDoesNotExposeError(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
context, _ := gin.CreateTestContext(recorder)
writePaymentCallbackAck(context, dto.PaymentCallbackAck{
writePaymentCallbackAck(context, service.PaymentCallbackAck{
StatusCode: 500,
ContentType: "text/plain; charset=utf-8",
Body: []byte("failure"),
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -15,41 +13,41 @@ func NewPermission(service *service.PermissionService) *Permission {
}
func (h *Permission) Buttons(c *gin.Context) {
var req dto.GetAuthorityButtonsRequest
var req service.GetAuthorityButtonsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
selected, err := h.service.SelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID)
if err != nil {
httpx.Fail(c, "查询失败")
Fail(c, "查询失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"selected": selected}, "查询成功")
Write(c, CodeSuccess, gin.H{"selected": selected}, "查询成功")
}
func (h *Permission) SetButtons(c *gin.Context) {
var req dto.SetAuthorityButtonsRequest
var req service.SetAuthorityButtonsRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.SetSelectedButtons(c.Request.Context(), req.AuthorityID, req.MenuID, req.Selected); err != nil {
httpx.Fail(c, "分配失败")
Fail(c, "分配失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "分配成功")
Write(c, CodeSuccess, gin.H{}, "分配成功")
}
func (h *Permission) CanRemoveButton(c *gin.Context) {
allowed, err := h.service.CanRemoveButton(c.Request.Context(), c.Query("id"))
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if !allowed {
httpx.Fail(c, "此按钮正在被使用无法删除")
Fail(c, "此按钮正在被使用无法删除")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
@@ -0,0 +1,11 @@
package handler
import "github.com/google/wire"
// ProviderSet wires the system HTTP handlers.
var ProviderSet = wire.NewSet(
NewAuthority, NewMenu, NewAPI, NewPermission, NewOrganization,
NewAnnouncement, NewEmail, NewPayment, NewTask, NewMedia, NewAudit,
NewExport, NewVersion, NewDictionary, NewParameter, NewAPIToken,
NewSystemConfig, NewPublic, NewUser, NewNavigation, NewSession, NewSet,
)
@@ -8,9 +8,7 @@ import (
"time"
"kra/app/system/internal/biz"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
@@ -36,7 +34,7 @@ func (h *Public) Captcha(c *gin.Context) {
keyLong, width, height := h.captchaConfig()
security, err := h.settings.CurrentSecurity(c.Request.Context())
if err != nil || security == nil {
httpx.Fail(c, "安全服务暂不可用")
Fail(c, "安全服务暂不可用")
return
}
openCaptcha := true
@@ -45,7 +43,7 @@ func (h *Public) Captcha(c *gin.Context) {
ttl := time.Duration(security.CaptchaTimeout) * time.Second
failures, counterErr := h.settings.EnsureLoginIPCounter(c.Request.Context(), c.ClientIP(), ttl)
if counterErr != nil {
httpx.Fail(c, "安全服务暂不可用")
Fail(c, "安全服务暂不可用")
return
}
openCaptcha = security.CaptchaOpen == 0 || failures > security.CaptchaOpen
@@ -53,24 +51,24 @@ func (h *Public) Captcha(c *gin.Context) {
driver := base64Captcha.NewDriverDigit(height, width, keyLong, 0.7, 80)
id, picture, _, err := base64Captcha.NewCaptcha(driver, h.store).Generate()
if err != nil {
httpx.Fail(c, "验证码获取失败")
Fail(c, "验证码获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功")
Write(c, CodeSuccess, gin.H{"captchaId": id, "picPath": picture, "captchaLength": keyLong, "openCaptcha": openCaptcha}, "验证码获取成功")
}
func (h *Public) Login(c *gin.Context) {
var req dto.LoginRequest
var req service.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Username == "" {
httpx.Fail(c, "Username值不能为空")
Fail(c, "Username值不能为空")
return
}
if req.Password == "" {
httpx.Fail(c, "Password值不能为空")
Fail(c, "Password值不能为空")
return
}
result, err := h.auth.Login(c.Request.Context(), &req, c.ClientIP(), c.Request.UserAgent())
@@ -83,66 +81,66 @@ func (h *Public) Login(c *gin.Context) {
if locked != nil {
minutes = locked.Minutes
}
httpx.Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试")
Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试")
case errors.Is(err, biz.ErrCaptchaInvalid):
httpx.Fail(c, "验证码错误")
Fail(c, "验证码错误")
case errors.Is(err, biz.ErrUserDisabled):
httpx.Fail(c, "用户被禁止登录")
Fail(c, "用户被禁止登录")
case errors.Is(err, biz.ErrTokenIssue):
httpx.Fail(c, "获取token失败")
Fail(c, "获取token失败")
case errors.Is(err, biz.ErrLoginState):
httpx.Fail(c, "设置登录状态失败")
Fail(c, "设置登录状态失败")
case errors.Is(err, biz.ErrTokenRevoke):
httpx.Fail(c, "jwt作废失败")
Fail(c, "jwt作废失败")
default:
httpx.Fail(c, "用户名不存在或者密码错误")
Fail(c, "用户名不存在或者密码错误")
}
return
}
maxAge := int(time.UnixMilli(result.ExpiresAt).Unix() - time.Now().Unix())
httpx.SetTokenCookie(c, result.Token, maxAge)
httpx.Write(c, httpx.CodeSuccess, result, "登录成功")
SetTokenCookie(c, result.Token, maxAge)
Write(c, CodeSuccess, result, "登录成功")
}
func (h *Public) CheckDatabase(c *gin.Context) {
initialized, err := h.system.IsInitialized(c.Request.Context())
if err != nil {
httpx.Fail(c, "数据库状态检查失败")
Fail(c, "数据库状态检查失败")
return
}
message := "数据库无需初始化"
if !initialized {
message = "前往初始化数据库"
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"needInit": !initialized}, message)
Write(c, CodeSuccess, gin.H{"needInit": !initialized}, message)
}
func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
return func(c *gin.Context) {
initialized, err := h.system.IsInitialized(c.Request.Context())
if err != nil {
httpx.Fail(c, "数据库状态检查失败")
Fail(c, "数据库状态检查失败")
return
}
if initialized {
httpx.Fail(c, "已存在数据库配置")
Fail(c, "已存在数据库配置")
return
}
var input dto.DatabaseInitRequest
var input service.DatabaseInitRequest
if c.ShouldBindJSON(&input) != nil {
httpx.Fail(c, "参数校验不通过")
Fail(c, "参数校验不通过")
return
}
routes := engine.Routes()
values := make([]dto.Route, 0, len(routes))
values := make([]service.Route, 0, len(routes))
for _, route := range routes {
values = append(values, dto.Route{Path: route.Path, Method: route.Method})
values = append(values, service.Route{Path: route.Path, Method: route.Method})
}
if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil {
httpx.Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化")
Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功")
Write(c, CodeSuccess, gin.H{}, "自动创建数据库成功")
}
}
@@ -2,7 +2,6 @@ package handler
import (
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -16,9 +15,9 @@ func (h *Session) Logout(c *gin.Context) {
token, _ = c.Cookie("x-token")
}
if err := h.tokens.BlacklistToken(c.Request.Context(), token); err != nil {
httpx.Fail(c, "jwt作废失败")
Fail(c, "jwt作废失败")
return
}
httpx.SetTokenCookie(c, "", -1)
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "jwt作废成功")
SetTokenCookie(c, "", -1)
Write(c, CodeSuccess, gin.H{}, "jwt作废成功")
}
@@ -1,9 +1,7 @@
package handler
import (
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -20,61 +18,61 @@ func NewSystemConfig(system *service.SystemConfigService, settings *service.Secu
func (h *SystemConfig) GetSecurity(c *gin.Context) {
value, err := h.settings.Security(c.Request.Context())
if err != nil {
httpx.Fail(c, "获取安全配置失败")
Fail(c, "获取安全配置失败")
return
}
httpx.Write(c, httpx.CodeSuccess, value, "获取成功")
Write(c, CodeSuccess, value, "获取成功")
}
func (h *SystemConfig) SetSecurity(c *gin.Context) {
var req dto.SecurityConfigRequest
var req service.SecurityConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
result, err := h.settings.SaveSecurityRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "设置安全配置失败")
Fail(c, "设置安全配置失败")
return
}
httpx.Write(c, httpx.CodeSuccess, result, "设置成功")
Write(c, CodeSuccess, result, "设置成功")
}
func (h *SystemConfig) Get(c *gin.Context) {
value, err := h.system.SystemConfig()
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, value, "获取成功")
Write(c, CodeSuccess, value, "获取成功")
}
func (h *SystemConfig) Set(c *gin.Context) {
var req dto.SetSystemConfigRequest
var req service.SetSystemConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.system.SaveSystemConfig(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "设置失败")
Fail(c, "设置失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *SystemConfig) Reload(c *gin.Context) {
if err := h.system.ReloadConfig(c.Request.Context()); err != nil {
httpx.Fail(c, "重载系统失败:"+err.Error())
Fail(c, "重载系统失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重载系统成功")
Write(c, CodeSuccess, gin.H{}, "重载系统成功")
}
func (h *SystemConfig) ServerInfo(c *gin.Context) {
server, err := h.system.ServerInfo()
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"server": server}, "获取成功")
Write(c, CodeSuccess, gin.H{"server": server}, "获取成功")
}
@@ -2,14 +2,10 @@ package handler
import (
"fmt"
"kra/app/system/internal/service"
"net/http"
"time"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"kra/app/system/internal/transport/middleware"
"github.com/gin-gonic/gin"
)
@@ -22,107 +18,107 @@ func NewTask(service *service.TaskService) *Task {
}
func (h *Task) Create(c *gin.Context) {
var req dto.TaskRequest
var req service.TaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
_, err := h.service.CreateRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "创建失败: "+err.Error())
Fail(c, "创建失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建成功")
Write(c, CodeSuccess, gin.H{}, "创建成功")
}
func (h *Task) Update(c *gin.Context) {
var req dto.TaskRequest
var req service.TaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "更新失败: "+err.Error())
Fail(c, "更新失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "更新成功")
Write(c, CodeSuccess, gin.H{}, "更新成功")
}
func (h *Task) Delete(c *gin.Context) {
var req dto.DeleteTaskRequest
var req service.DeleteTaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Task) Toggle(c *gin.Context) {
var req dto.ToggleTaskRequest
var req service.ToggleTaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Toggle(c.Request.Context(), req.ID, req.Enabled); err != nil {
httpx.Fail(c, "操作失败: "+err.Error())
Fail(c, "操作失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "操作成功")
Write(c, CodeSuccess, gin.H{}, "操作成功")
}
func (h *Task) Trigger(c *gin.Context) {
var req dto.DeleteTaskRequest
var req service.DeleteTaskRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Trigger(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "触发失败: "+err.Error())
Fail(c, "触发失败: "+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "已触发, 执行结果见执行日志")
Write(c, CodeSuccess, gin.H{}, "已触发, 执行结果见执行日志")
}
func (h *Task) List(c *gin.Context) {
var req dto.TaskListRequest
var req service.TaskListRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.ListRequest(c.Request.Context(), req.Page, req.PageSize, req.Name, req.ExecutorType, req.Enabled)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Task) Logs(c *gin.Context) {
var req dto.TaskLogListRequest
var req service.TaskLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
items, total, err := h.service.Logs(c.Request.Context(), req.Page, req.PageSize, req.TaskID, req.Status)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Task) Methods(c *gin.Context) {
httpx.Write(c, httpx.CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功")
Write(c, CodeSuccess, gin.H{"methods": h.service.RegisteredMethods()}, "获取成功")
}
func (h *Task) AlertStream(c *gin.Context) {
claims := middleware.Claims(c)
claims := Claims(c)
if claims == nil || claims.ID == 0 {
httpx.Fail(c, "未获取到用户身份")
Fail(c, "未获取到用户身份")
return
}
c.Header("Content-Type", "text/event-stream; charset=utf-8")
@@ -131,7 +127,7 @@ func (h *Task) AlertStream(c *gin.Context) {
c.Header("X-Accel-Buffering", "no")
flusher, ok := c.Writer.(http.Flusher)
if !ok {
httpx.Fail(c, "SSE 不受支持")
Fail(c, "SSE 不受支持")
return
}
// Remove the normal server deadline for long-lived SSE connections.
@@ -1,14 +1,10 @@
package handler
import (
"kra/app/system/internal/service"
"strconv"
"time"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"kra/app/system/internal/transport/middleware"
"github.com/gin-gonic/gin"
)
@@ -22,236 +18,236 @@ func NewUser(user *service.UserService, auth *service.AuthService) *User {
}
func (h *User) List(c *gin.Context) {
var req dto.UserListRequest
var req service.UserListRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Page == 0 {
httpx.Fail(c, "Page值不能为空")
Fail(c, "Page值不能为空")
return
}
if req.PageSize == 0 {
httpx.Fail(c, "PageSize值不能为空")
Fail(c, "PageSize值不能为空")
return
}
items, total, err := h.service.ListUsersRequest(c.Request.Context(), &req)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *User) Create(c *gin.Context) {
var req dto.UserRequest
var req service.UserRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Username == "" {
httpx.Fail(c, "Username值不能为空")
Fail(c, "Username值不能为空")
return
}
if req.Password == "" {
httpx.Fail(c, "Password值不能为空")
Fail(c, "Password值不能为空")
return
}
if req.NickName == "" {
httpx.Fail(c, "NickName值不能为空")
Fail(c, "NickName值不能为空")
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
result, err := h.service.CreateUserRequest(c.Request.Context(), &req)
if err != nil {
if service.IsPasswordPolicyError(err) {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if result == nil {
result = &dto.UserResponse{}
result = &service.UserResponse{}
}
httpx.Write(c, httpx.CodeError, gin.H{"user": result}, "注册失败")
Write(c, CodeError, gin.H{"user": result}, "注册失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"user": result}, "注册成功")
Write(c, CodeSuccess, gin.H{"user": result}, "注册成功")
}
func (h *User) Update(c *gin.Context) {
var req dto.UserRequest
var req service.UserRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
if err := h.service.UpdateUserRequest(c.Request.Context(), &req); err != nil {
httpx.Fail(c, "修改失败")
Fail(c, "修改失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *User) UpdateSelf(c *gin.Context) {
claims := middleware.Claims(c)
claims := Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
var req dto.SelfUserRequest
var req service.SelfUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.UpdateSelfUserRequest(c.Request.Context(), claims.ID, &req); err != nil {
httpx.Fail(c, "修改失败")
Fail(c, "修改失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *User) Delete(c *gin.Context) {
var req dto.IDRequest
var req service.IDRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.ID == 0 {
httpx.Fail(c, "ID值不能为空")
Fail(c, "ID值不能为空")
return
}
claims := middleware.Claims(c)
claims := Claims(c)
if claims != nil && claims.ID == req.ID {
httpx.Fail(c, "删除失败, 无法删除自己。")
Fail(c, "删除失败, 无法删除自己。")
return
}
if err := h.service.DeleteUser(c.Request.Context(), req.ID); err != nil {
httpx.Fail(c, "删除失败")
Fail(c, "删除失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *User) ResetPassword(c *gin.Context) {
var req dto.ResetPasswordRequest
var req service.ResetPasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.ResetPassword(c.Request.Context(), req.ID, req.Password); err != nil {
if service.IsPasswordPolicyError(err) {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Fail(c, "重置失败"+err.Error())
Fail(c, "重置失败"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "重置成功")
Write(c, CodeSuccess, gin.H{}, "重置成功")
}
func (h *User) ChangePassword(c *gin.Context) {
claims := middleware.Claims(c)
var req dto.ChangePasswordRequest
claims := Claims(c)
var req service.ChangePasswordRequest
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.Password == "" {
httpx.Fail(c, "Password值不能为空")
Fail(c, "Password值不能为空")
return
}
if req.NewPassword == "" {
httpx.Fail(c, "NewPassword值不能为空")
Fail(c, "NewPassword值不能为空")
return
}
if err := h.service.ChangePassword(c.Request.Context(), claims.ID, req.Password, req.NewPassword); err != nil {
if service.IsPasswordPolicyError(err) {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
httpx.Fail(c, "修改失败,原密码与当前账户不符")
Fail(c, "修改失败,原密码与当前账户不符")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功")
Write(c, CodeSuccess, gin.H{}, "修改成功")
}
func (h *User) SetSelfSetting(c *gin.Context) {
claims := middleware.Claims(c)
claims := Claims(c)
var setting map[string]any
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
if err := c.ShouldBindJSON(&setting); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.SetUserSetting(c.Request.Context(), claims.ID, setting); err != nil {
httpx.Fail(c, "设置失败")
Fail(c, "设置失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "设置成功")
Write(c, CodeSuccess, gin.H{}, "设置成功")
}
func (h *User) SetAuthorities(c *gin.Context) {
var req dto.UserAuthoritiesRequest
var req service.UserAuthoritiesRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.SetUserAuthorities(c.Request.Context(), req.ID, req.AuthorityIDs); err != nil {
httpx.Fail(c, "修改失败")
Fail(c, "修改失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功")
Write(c, CodeSuccess, gin.H{}, "修改成功")
}
func (h *User) SwitchAuthority(c *gin.Context) {
claims := middleware.Claims(c)
var req dto.SwitchAuthorityRequest
claims := Claims(c)
var req service.SwitchAuthorityRequest
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if req.AuthorityID == 0 {
httpx.Fail(c, "AuthorityId值不能为空")
Fail(c, "AuthorityId值不能为空")
return
}
login, err := h.auth.SwitchAuthority(c.Request.Context(), claims, req.AuthorityID)
if err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
c.Header("new-token", login.Token)
c.Header("new-expires-at", strconv.FormatInt(login.ExpiresAt/1000, 10))
maxAge := int(time.UnixMilli(login.ExpiresAt).Unix() - time.Now().Unix())
httpx.SetTokenCookie(c, login.Token, maxAge)
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "修改成功")
SetTokenCookie(c, login.Token, maxAge)
Write(c, CodeSuccess, gin.H{}, "修改成功")
}
func (h *User) Get(c *gin.Context) {
claims := middleware.Claims(c)
claims := Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
result, err := h.service.UserByUUID(c.Request.Context(), claims.UUID)
if err != nil {
httpx.Fail(c, "获取失败")
Fail(c, "获取失败")
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{"userInfo": result}, "获取成功")
Write(c, CodeSuccess, gin.H{"userInfo": result}, "获取成功")
}
@@ -8,9 +8,7 @@ import (
"time"
"kra/app/system/internal/biz"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -30,10 +28,10 @@ func versionStage(err error) (biz.VersionStage, bool) {
func (h *Version) Delete(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
if err := h.service.DeleteVersions(c.Request.Context(), []uint{uint(id)}); err != nil {
httpx.Fail(c, "删除失败:"+err.Error())
Fail(c, "删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "删除成功")
Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Version) DeleteMany(c *gin.Context) {
raw := c.QueryArray("IDs[]")
@@ -43,24 +41,24 @@ func (h *Version) DeleteMany(c *gin.Context) {
ids = append(ids, uint(id))
}
if err := h.service.DeleteVersions(c.Request.Context(), ids); err != nil {
httpx.Fail(c, "批量删除失败:"+err.Error())
Fail(c, "批量删除失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "批量删除成功")
Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Version) Find(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("ID"), 10, 64)
item, err := h.service.Version(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "查询失败:"+err.Error())
Fail(c, "查询失败:"+err.Error())
return
}
httpx.OKWithData(c, item)
OKWithData(c, item)
}
func (h *Version) List(c *gin.Context) {
var req dto.VersionSearchRequest
var req service.VersionSearchRequest
if err := c.ShouldBindQuery(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
name, code := "", ""
@@ -76,47 +74,47 @@ func (h *Version) List(c *gin.Context) {
}
items, total, err := h.service.Versions(c.Request.Context(), req.Page, req.PageSize, name, code, createdAtRange)
if err != nil {
httpx.Fail(c, "获取失败:"+err.Error())
Fail(c, "获取失败:"+err.Error())
return
}
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Version) Export(c *gin.Context) {
var req dto.ExportVersionRequest
var req service.ExportVersionRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, err.Error())
Fail(c, err.Error())
return
}
if err := h.service.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil {
switch stage, _ := versionStage(err); stage {
case biz.VersionStageMenus:
httpx.Fail(c, "获取菜单数据失败:"+err.Error())
Fail(c, "获取菜单数据失败:"+err.Error())
case biz.VersionStageAPIs:
httpx.Fail(c, "获取API数据失败:"+err.Error())
Fail(c, "获取API数据失败:"+err.Error())
case biz.VersionStageDictionaries:
httpx.Fail(c, "获取字典数据失败:"+err.Error())
Fail(c, "获取字典数据失败:"+err.Error())
case biz.VersionStageJSON:
httpx.Fail(c, "JSON序列化失败:"+err.Error())
Fail(c, "JSON序列化失败:"+err.Error())
case biz.VersionStageSave:
httpx.Fail(c, "保存版本记录失败:"+err.Error())
Fail(c, "保存版本记录失败:"+err.Error())
default:
httpx.Fail(c, "创建发版失败:"+err.Error())
Fail(c, "创建发版失败:"+err.Error())
}
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "创建发版成功")
Write(c, CodeSuccess, gin.H{}, "创建发版成功")
}
func (h *Version) Download(c *gin.Context) {
rawID := c.Query("ID")
if rawID == "" {
httpx.Fail(c, "版本ID不能为空")
Fail(c, "版本ID不能为空")
return
}
id, _ := strconv.ParseUint(rawID, 10, 64)
raw, code, err := h.service.VersionData(c.Request.Context(), uint(id))
if err != nil {
httpx.Fail(c, "获取版本记录失败:"+err.Error())
Fail(c, "获取版本记录失败:"+err.Error())
return
}
name := fmt.Sprintf("version_%s_%s.json", code, time.Now().Format("20060102150405"))
@@ -126,27 +124,27 @@ func (h *Version) Download(c *gin.Context) {
c.Data(http.StatusOK, "application/json", []byte(raw))
}
func (h *Version) Import(c *gin.Context) {
var req dto.ImportVersionRequest
var req service.ImportVersionRequest
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Fail(c, "解析JSON数据失败:"+err.Error())
Fail(c, "解析JSON数据失败:"+err.Error())
return
}
if err := h.service.ImportRequest(c.Request.Context(), &req); err != nil {
if errors.Is(err, biz.ErrInvalidVersion) {
httpx.Fail(c, "版本信息格式错误")
Fail(c, "版本信息格式错误")
return
}
switch stage, _ := versionStage(err); stage {
case biz.VersionStageMenus:
httpx.Fail(c, "导入菜单失败: "+err.Error())
Fail(c, "导入菜单失败: "+err.Error())
case biz.VersionStageAPIs:
httpx.Fail(c, "导入API失败: "+err.Error())
Fail(c, "导入API失败: "+err.Error())
case biz.VersionStageDictionaries:
httpx.Fail(c, "导入字典失败: "+err.Error())
Fail(c, "导入字典失败: "+err.Error())
default:
httpx.Fail(c, "导入失败:"+err.Error())
Fail(c, "导入失败:"+err.Error())
}
return
}
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "导入成功")
Write(c, CodeSuccess, gin.H{}, "导入成功")
}
@@ -6,7 +6,6 @@ import (
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -20,7 +19,7 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu
return func(c *gin.Context) {
claims := Claims(c)
if claims == nil {
httpx.NoAuth(c, "未登录或非法访问")
NoAuth(c, "未登录或非法访问")
return
}
path := c.Request.URL.Path
@@ -30,13 +29,13 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu
}
allowed, err := access.Authorize(c.Request.Context(), claims.AuthorityID, policyPath, c.Request.Method)
if err != nil || !allowed {
httpx.Write(c, httpx.CodeError, gin.H{}, "权限不足")
Write(c, CodeError, gin.H{}, "权限不足")
c.Abort()
return
}
requestContext, err := access.ContextWithDataScope(c.Request.Context(), claims.AuthorityID, claims.ID)
if err != nil {
httpx.Write(c, httpx.CodeError, gin.H{}, "数据权限解析失败")
Write(c, CodeError, gin.H{}, "数据权限解析失败")
c.Abort()
return
}
@@ -11,7 +11,6 @@ import (
"kra/app/system/internal/biz"
"kra/app/system/internal/conf"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -41,7 +40,7 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H
c.Header("X-Kra-Version", version)
requestReadFailed := c.Request.ContentLength > bodyLimit
if requestReadFailed {
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"})
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"})
} else if c.Request.Body != nil && !mediaUpload {
limited := http.MaxBytesReader(c.Writer, c.Request.Body, bodyLimit)
var err error
@@ -50,9 +49,9 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H
requestReadFailed = true
var tooLarge *http.MaxBytesError
if errors.As(err, &tooLarge) {
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": httpx.CodeError, "msg": "请求体超过大小上限"})
c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, gin.H{"code": CodeError, "msg": "请求体超过大小上限"})
} else {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": httpx.CodeError, "msg": "请求体读取失败"})
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"code": CodeError, "msg": "请求体读取失败"})
}
} else {
c.Request.Body = io.NopCloser(bytes.NewReader(requestBody))
@@ -13,7 +13,6 @@ import (
"time"
"kra/app/system/internal/conf"
"kra/app/system/internal/dto"
"kra/app/system/internal/service"
"github.com/gin-gonic/gin"
@@ -21,7 +20,7 @@ import (
const ctxOperationAuditPersistFailedKey = "operation_audit_persist_failed"
func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.HandlerFunc {
func OperationAudit(runtime *conf.Runtime, recorder *service.AuditRecorder) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
if !recordsOperation(c.Request.Method, path) {
@@ -81,7 +80,7 @@ func OperationAudit(runtime *conf.Runtime, service *service.AuditRecorder) gin.H
responseBody = "[超出记录长度]"
}
errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String()
if err := service.RecordOperationRequest(c.Request.Context(), &dto.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes), Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil {
if err := recorder.RecordOperationRequest(c.Request.Context(), &service.OperationRecordRequest{IP: c.ClientIP(), Method: c.Request.Method, Path: path, Status: status, LatencyMS: time.Since(started).Milliseconds(), Agent: c.Request.UserAgent(), ErrorMessage: errorMessage, Body: operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes), Response: responseBody, UserID: userID, RequestID: stringValue(requestID), TraceID: stringValueFromContext(c, "trace_id"), DeviceID: c.GetHeader("X-Device-Id")}); err != nil {
// Preserve the business response, but expose audit persistence failures
// to the global access/error logging pipeline.
c.Set(ctxOperationAuditPersistFailedKey, true)
@@ -8,7 +8,6 @@ import (
"kra/app/system/internal/biz"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
"golang.org/x/sync/singleflight"
@@ -25,7 +24,7 @@ func Auth(auth *service.AuthService) gin.HandlerFunc {
token, _ = c.Cookie("x-token")
}
if token == "" {
httpx.NoAuth(c, "未登录或非法访问,请登录")
NoAuth(c, "未登录或非法访问,请登录")
return
}
value, err, _ := refreshTokens.Do(token, func() (any, error) {
@@ -45,15 +44,15 @@ func Auth(auth *service.AuthService) gin.HandlerFunc {
case errors.Is(err, biz.ErrTokenDisabled):
message = "您的帐户异地登陆或令牌失效"
}
httpx.SetTokenCookie(c, "", -1)
httpx.NoAuth(c, message)
SetTokenCookie(c, "", -1)
NoAuth(c, message)
return
}
authentication := value.(*biz.TokenAuthentication)
if authentication.Refreshed != nil {
c.Header("new-token", authentication.Refreshed.Value)
c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10))
httpx.SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds()))
SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds()))
}
c.Set(claimsKey, authentication.Claims)
c.Next()
@@ -78,6 +77,6 @@ func MustChangePassword() gin.HandlerFunc {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"})
c.AbortWithStatusJSON(http.StatusConflict, Response{Code: CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"})
}
}
@@ -6,8 +6,6 @@ import (
"log/slog"
"strings"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -26,7 +24,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc {
if auditPersistFailed != true && (strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/") || c.Writer.Status() >= 300 && c.Writer.Status() < 500) {
return
}
var response httpx.Response
var response Response
var body []byte
if value, ok := c.Get(ctxRespBufferKey); ok {
if buffer, valid := value.(*bytes.Buffer); valid {
@@ -36,7 +34,7 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc {
if json.Unmarshal(body, &response) != nil && privateErrors == "" {
return
}
if response.Code == httpx.CodeSuccess && privateErrors == "" {
if response.Code == CodeSuccess && privateErrors == "" {
return
}
if privateErrors == "" && expectedClientFailure(response.Msg) {
@@ -0,0 +1,18 @@
package middleware
import (
"github.com/gin-gonic/gin"
"kra/app/system/internal/server/httpx"
)
const (
CodeSuccess = httpx.CodeSuccess
CodeError = httpx.CodeError
CodePasswordChangeRequired = httpx.CodePasswordChangeRequired
)
type Response = httpx.Response
func Write(c *gin.Context, code int, data any, message string) { httpx.Write(c, code, data, message) }
func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) }
func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) }
@@ -1,13 +1,11 @@
package middleware
import (
"kra/app/system/internal/service"
"net/http"
"strings"
"time"
"kra/app/system/internal/service"
"kra/app/system/internal/transport/httpx"
"github.com/gin-gonic/gin"
)
@@ -20,7 +18,7 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
}
config, err := settings.CurrentSecurity(c.Request.Context())
if err != nil || config == nil {
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"})
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"})
return
}
if !config.LimitEnable {
@@ -34,11 +32,11 @@ func SecurityRateLimit(settings *service.SecurityService) gin.HandlerFunc {
key := "KRA_SecLimit" + c.ClientIP() + c.FullPath()
count, cacheErr := settings.IncrementRateLimit(c.Request.Context(), key, time.Duration(window)*time.Second)
if cacheErr != nil {
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": httpx.CodeError, "msg": "安全服务暂不可用"})
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"code": CodeError, "msg": "安全服务暂不可用"})
return
}
if int(count) > config.LimitCount {
c.JSON(200, gin.H{"code": httpx.CodeError, "msg": "请求太过频繁,请稍后再试"})
c.JSON(200, gin.H{"code": CodeError, "msg": "请求太过频繁,请稍后再试"})
c.Abort()
return
}
@@ -1,10 +1,8 @@
package server
import (
"kra/app/system/internal/transport/handler"
"github.com/google/wire"
)
// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewGinEngineWithRuntime, NewGinServer, NewRoutes, handler.NewAuthority, handler.NewMenu, handler.NewAPI, handler.NewPermission, handler.NewOrganization, handler.NewAnnouncement, handler.NewEmail, handler.NewPayment, handler.NewTask, handler.NewMedia, handler.NewAudit, handler.NewExport, handler.NewVersion, handler.NewDictionary, handler.NewParameter, handler.NewAPIToken, handler.NewSystemConfig, handler.NewPublic, handler.NewUser, handler.NewNavigation, handler.NewSession, handler.NewSet)
var ProviderSet = wire.NewSet(NewGinEngineWithRuntime, NewGinServer)
@@ -0,0 +1,26 @@
package router
import "kra/app/system/internal/server/handler"
type Set = handler.Set
type Announcement = handler.Announcement
type API = handler.API
type APIToken = handler.APIToken
type Audit = handler.Audit
type Authority = handler.Authority
type Dictionary = handler.Dictionary
type Email = handler.Email
type Export = handler.Export
type Media = handler.Media
type Menu = handler.Menu
type Navigation = handler.Navigation
type Organization = handler.Organization
type Parameter = handler.Parameter
type Payment = handler.Payment
type Permission = handler.Permission
type Public = handler.Public
type Session = handler.Session
type SystemConfig = handler.SystemConfig
type Task = handler.Task
type User = handler.User
type Version = handler.Version
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterAnnouncement(private, public *gin.RouterGroup, h *handler.Announcement) {
func RegisterAnnouncement(private, public *gin.RouterGroup, h *Announcement) {
privateInfo := private.Group("/info")
privateInfo.POST("/createInfo", h.Create)
privateInfo.DELETE("/deleteInfo", h.Delete)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *handler.API) {
func RegisterAPI(group, public *gin.RouterGroup, engine *gin.Engine, h *API) {
api := group.Group("/api")
api.POST("/getApiList", h.List)
api.POST("/getAllApis", h.All)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterAPIToken(group *gin.RouterGroup, handler *handler.APIToken) {
func RegisterAPIToken(group *gin.RouterGroup, handler *APIToken) {
router := group.Group("/sysApiToken")
router.POST("/createApiToken", handler.Create)
router.POST("/getApiTokenList", handler.List)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterAudit(group, public *gin.RouterGroup, h *handler.Audit) {
func RegisterAudit(group, public *gin.RouterGroup, h *Audit) {
operations := group.Group("/sysOperationRecord")
operations.GET("/getSysOperationRecordList", h.Operations)
operations.GET("/findSysOperationRecord", h.Operation)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterAuthority(group *gin.RouterGroup, h *handler.Authority) {
func RegisterAuthority(group *gin.RouterGroup, h *Authority) {
router := group.Group("/authority")
router.POST("/getAuthorityList", h.List)
router.POST("/createAuthority", h.Create)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterDictionary(group *gin.RouterGroup, h *handler.Dictionary) {
func RegisterDictionary(group *gin.RouterGroup, h *Dictionary) {
dictionaries := group.Group("/sysDictionary")
dictionaries.POST("/createSysDictionary", h.Create)
dictionaries.PUT("/updateSysDictionary", h.Update)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterEmail(group *gin.RouterGroup, h *handler.Email) {
func RegisterEmail(group *gin.RouterGroup, h *Email) {
email := group.Group("/email")
email.POST("/emailTest", h.Test)
email.POST("/sendEmail", h.Send)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterExport(group, public *gin.RouterGroup, h *handler.Export) {
func RegisterExport(group, public *gin.RouterGroup, h *Export) {
router := group.Group("/sysExportTemplate")
router.POST("/createSysExportTemplate", h.Create)
router.PUT("/updateSysExportTemplate", h.Update)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterMedia(group *gin.RouterGroup, h *handler.Media) {
func RegisterMedia(group *gin.RouterGroup, h *Media) {
files := group.Group("/fileUploadAndDownload")
files.POST("/upload", h.Upload)
files.POST("/getFileList", h.List)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterMenu(group *gin.RouterGroup, h *handler.Menu) {
func RegisterMenu(group *gin.RouterGroup, h *Menu) {
router := group.Group("/menu")
router.POST("/getMenuList", h.List)
router.POST("/getBaseMenuTree", h.Tree)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterOrganization(group *gin.RouterGroup, h *handler.Organization) {
func RegisterOrganization(group *gin.RouterGroup, h *Organization) {
departments := group.Group("/department")
departments.POST("/getDepartmentList", h.ListDepartments)
departments.POST("/createDepartment", h.CreateDepartment)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterParameter(group *gin.RouterGroup, handler *handler.Parameter) {
func RegisterParameter(group *gin.RouterGroup, handler *Parameter) {
router := group.Group("/sysParams")
router.POST("/createSysParams", handler.Create)
router.PUT("/updateSysParams", handler.Update)
@@ -2,17 +2,20 @@ package router
import (
"github.com/gin-gonic/gin"
"kra/app/system/internal/transport/handler"
)
func RegisterPayment(group, public *gin.RouterGroup, h *handler.Payment) {
func RegisterPayment(group, public *gin.RouterGroup, h *Payment) {
payment := group.Group("/payment")
payment.GET("/configs", h.Configs)
payment.POST("/config", h.SaveConfig)
payment.GET("/orders", h.Orders)
payment.POST("/order", h.Order)
payment.GET("/orders/:provider/:tradeNo", h.OrderByPath)
payment.POST("/create", h.Create)
payment.POST("/query", h.Query)
payment.POST("/refund", h.Refund)
payment.POST("/orders/:provider/:tradeNo/refund", h.RefundByPath)
payment.POST("/orders/:provider/:tradeNo/fulfill", h.FulfillByPath)
payment.POST("/fulfill", h.Fulfill)
public.POST("/payment/callback/:provider", h.Callback)
}
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterPermission(group *gin.RouterGroup, h *handler.Permission) {
func RegisterPermission(group *gin.RouterGroup, h *Permission) {
buttons := group.Group("/authorityBtn")
buttons.POST("/getAuthorityBtn", h.Buttons)
buttons.POST("/setAuthorityBtn", h.SetButtons)
@@ -0,0 +1,6 @@
package router
import "github.com/google/wire"
// ProviderSet wires the system route registrar.
var ProviderSet = wire.NewSet(NewRoutes)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *handler.Public) {
func RegisterPublic(group *gin.RouterGroup, engine *gin.Engine, handler *Public) {
base := group.Group("/base")
base.POST("/captcha", handler.Captcha)
base.POST("/login", handler.Login)
@@ -0,0 +1,42 @@
package router
import (
platformmodule "kra/pkg/module"
"github.com/gin-gonic/gin"
)
type Routes struct {
handlers *Set
}
func NewRoutes(handlers *Set) *Routes { return &Routes{handlers: handlers} }
var _ platformmodule.RouteRegistrar = (*Routes)(nil)
func (r *Routes) RegisterRoutes(public, private *gin.RouterGroup, engine *gin.Engine) {
if r == nil || r.handlers == nil {
return
}
RegisterPublic(public, engine, r.handlers.Public)
RegisterUser(private, r.handlers.User)
RegisterNavigation(private, r.handlers.Navigation)
RegisterSession(private, r.handlers.Session)
RegisterAuthority(private, r.handlers.Authority)
RegisterMenu(private, r.handlers.Menu)
RegisterAPI(private, public, engine, r.handlers.API)
RegisterPermission(private, r.handlers.Permission)
RegisterOrganization(private, r.handlers.Organization)
RegisterDictionary(private, r.handlers.Dictionary)
RegisterParameter(private, r.handlers.Parameter)
RegisterAPIToken(private, r.handlers.APIToken)
RegisterSystemConfig(private, r.handlers.SystemConfig)
RegisterVersion(private, r.handlers.Version)
RegisterExport(private, public, r.handlers.Export)
RegisterAudit(private, public, r.handlers.Audit)
RegisterTask(private, r.handlers.Task)
RegisterMedia(private, r.handlers.Media)
RegisterAnnouncement(private, public, r.handlers.Announcement)
RegisterEmail(private, r.handlers.Email)
RegisterPayment(private, public, r.handlers.Payment)
}
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterSystemConfig(group *gin.RouterGroup, handler *handler.SystemConfig) {
func RegisterSystemConfig(group *gin.RouterGroup, handler *SystemConfig) {
security := group.Group("/securityConfig")
security.GET("/getSecurityConfig", handler.GetSecurity)
security.POST("/setSecurityConfig", handler.SetSecurity)
@@ -1,12 +1,10 @@
package router
import (
"kra/app/system/internal/transport/handler"
"github.com/gin-gonic/gin"
)
func RegisterTask(group *gin.RouterGroup, h *handler.Task) {
func RegisterTask(group *gin.RouterGroup, h *Task) {
router := group.Group("/timedTask")
router.POST("/createTimedTask", h.Create)
router.PUT("/updateTimedTask", h.Update)

Some files were not shown because too many files have changed in this diff Show More