任务三
This commit is contained in:
parent
3c0f3b7165
commit
be3b6641a6
|
|
@ -11,73 +11,73 @@ server:
|
|||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: kratos-admin
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
signing_key: kratos-admin
|
||||
expires_time: 7d
|
||||
buffer_time: 1d
|
||||
issuer: kratos-admin
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: local
|
||||
db-type: mysql
|
||||
oss-type: local
|
||||
use-redis: false
|
||||
use-multipoint: false
|
||||
iplimit-count: 15000
|
||||
iplimit-time: 3600
|
||||
router-prefix: ""
|
||||
use-strict-auth: false
|
||||
db_type: mysql
|
||||
oss_type: local
|
||||
use_redis: false
|
||||
use_multipoint: false
|
||||
iplimit_count: 15000
|
||||
iplimit_time: 3600
|
||||
router_prefix: ""
|
||||
use_strict_auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0
|
||||
open-captcha-timeout: 3600
|
||||
key_long: 6
|
||||
img_width: 240
|
||||
img_height: 80
|
||||
open_captcha: 0
|
||||
open_captcha_timeout: 3600
|
||||
|
||||
# mysql configuration
|
||||
mysql:
|
||||
path: 127.0.0.1
|
||||
port: "3306"
|
||||
config: charset=utf8mb4&parseTime=True&loc=Local
|
||||
db-name: kratos_admin
|
||||
db_name: spa
|
||||
username: root
|
||||
password: root
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: info
|
||||
log-zap: false
|
||||
password: Xu950329.
|
||||
max_idle_conns: 10
|
||||
max_open_conns: 100
|
||||
log_mode: info
|
||||
log_zap: false
|
||||
|
||||
# pgsql configuration
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
db_name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
max_idle_conns: 10
|
||||
max_open_conns: 100
|
||||
log_mode: ""
|
||||
log_zap: false
|
||||
|
||||
# sqlite configuration
|
||||
sqlite:
|
||||
path: ""
|
||||
db-name: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db_name: ""
|
||||
max_idle_conns: 10
|
||||
max_open_conns: 100
|
||||
log_mode: ""
|
||||
log_zap: false
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
use-cluster: false
|
||||
use_cluster: false
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
cluster-addrs:
|
||||
cluster_addrs:
|
||||
- "127.0.0.1:7000"
|
||||
- "127.0.0.1:7001"
|
||||
- "127.0.0.1:7002"
|
||||
|
|
@ -85,74 +85,74 @@ redis:
|
|||
# local storage configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
store_path: uploads/file
|
||||
|
||||
# qiniu configuration
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
img_path: ""
|
||||
use_https: false
|
||||
access_key: ""
|
||||
secret_key: ""
|
||||
use_cdn_domains: false
|
||||
|
||||
# minio configuration
|
||||
minio:
|
||||
endpoint: ""
|
||||
access-key-id: ""
|
||||
access-key-secret: ""
|
||||
bucket-name: ""
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: ""
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
bucket_name: ""
|
||||
use_ssl: false
|
||||
base_path: ""
|
||||
bucket_url: ""
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
aliyun_oss:
|
||||
endpoint: ""
|
||||
access-key-id: ""
|
||||
access-key-secret: ""
|
||||
bucket-name: ""
|
||||
bucket-url: ""
|
||||
base-path: ""
|
||||
access_key_id: ""
|
||||
access_key_secret: ""
|
||||
bucket_name: ""
|
||||
bucket_url: ""
|
||||
base_path: ""
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
tencent_cos:
|
||||
bucket: ""
|
||||
region: ""
|
||||
secret-id: ""
|
||||
secret-key: ""
|
||||
base-url: ""
|
||||
path-prefix: ""
|
||||
secret_id: ""
|
||||
secret_key: ""
|
||||
base_url: ""
|
||||
path_prefix: ""
|
||||
|
||||
# aws s3 configuration
|
||||
aws-s3:
|
||||
aws_s3:
|
||||
bucket: ""
|
||||
region: ""
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: ""
|
||||
secret-key: ""
|
||||
base-url: ""
|
||||
path-prefix: ""
|
||||
s3_force_path_style: false
|
||||
disable_ssl: false
|
||||
secret_id: ""
|
||||
secret_key: ""
|
||||
base_url: ""
|
||||
path_prefix: ""
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
cloudflare_r2:
|
||||
bucket: ""
|
||||
base-url: ""
|
||||
base_url: ""
|
||||
path: uploads
|
||||
account-id: ""
|
||||
access-key-id: ""
|
||||
secret-access-key: ""
|
||||
account_id: ""
|
||||
access_key_id: ""
|
||||
secret_access_key: ""
|
||||
|
||||
# huawei obs configuration
|
||||
huawei-obs:
|
||||
huawei_obs:
|
||||
path: ""
|
||||
bucket: ""
|
||||
endpoint: ""
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
access_key: ""
|
||||
secret_key: ""
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
|
|
@ -160,7 +160,7 @@ email:
|
|||
port: 465
|
||||
from: ""
|
||||
host: ""
|
||||
is-ssl: true
|
||||
is_ssl: true
|
||||
secret: ""
|
||||
nickname: ""
|
||||
|
||||
|
|
@ -172,11 +172,11 @@ excel:
|
|||
cors:
|
||||
mode: allow-all
|
||||
whitelist:
|
||||
- allow-origin: "*"
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id
|
||||
allow-methods: POST,GET,OPTIONS,DELETE,PUT
|
||||
expose-headers: Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type
|
||||
allow-credentials: true
|
||||
- allow_origin: "*"
|
||||
allow_headers: Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id
|
||||
allow_methods: POST,GET,OPTIONS,DELETE,PUT
|
||||
expose_headers: Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type
|
||||
allow_credentials: true
|
||||
|
||||
# zap logger configuration
|
||||
zap:
|
||||
|
|
@ -184,8 +184,8 @@ zap:
|
|||
format: console
|
||||
prefix: "[kratos-admin]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
show_line: true
|
||||
encode_level: LowercaseColorLevelEncoder
|
||||
stacktrace_key: stacktrace
|
||||
log_in_console: true
|
||||
retention_day: -1
|
||||
|
|
|
|||
17
go.mod
17
go.mod
|
|
@ -23,17 +23,14 @@ require (
|
|||
github.com/qiniu/go-sdk/v7 v7.25.2
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
go.einride.tech/aip v0.78.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2
|
||||
google.golang.org/grpc v1.74.2
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gen v0.3.27
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/plugin/dbresolver v1.6.2
|
||||
|
|
@ -73,6 +70,7 @@ require (
|
|||
github.com/go-kratos/aegis v0.2.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/form/v4 v4.2.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
|
|
@ -89,19 +87,24 @@ require (
|
|||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.6.0 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
|
|
@ -114,9 +117,13 @@ require (
|
|||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/datatypes v1.2.4 // indirect
|
||||
gorm.io/driver/postgres v1.5.11 // indirect
|
||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||
gorm.io/driver/sqlserver v1.5.3 // indirect
|
||||
gorm.io/hints v1.1.0 // indirect
|
||||
modernc.org/fileutil v1.0.0 // indirect
|
||||
|
|
|
|||
21
go.sum
21
go.sum
|
|
@ -120,6 +120,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
|
||||
|
|
@ -153,6 +155,7 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
|
|||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
|
|
@ -216,6 +219,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
|
|||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
|
|
@ -245,6 +250,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgm
|
|||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
|
|
@ -269,6 +276,12 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
|
|||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
|
@ -287,6 +300,10 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod
|
|||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
|
||||
|
|
@ -294,6 +311,8 @@ github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmji
|
|||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.einride.tech/aip v0.78.0 h1:nbnM/OuGt5Pyz/r2KOxB6Hp+ey2e0+MNnfIPBtY45pY=
|
||||
go.einride.tech/aip v0.78.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
|
|
@ -362,8 +381,10 @@ golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
|||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
|
|||
|
|
@ -7,4 +7,14 @@ import (
|
|||
)
|
||||
|
||||
// ProviderSet is biz providers.
|
||||
var ProviderSet = wire.NewSet(system.NewUserUsecase, system.NewApiUsecase)
|
||||
var ProviderSet = wire.NewSet(
|
||||
system.NewUserUsecase,
|
||||
system.NewApiUsecase,
|
||||
system.NewAuthorityUsecase,
|
||||
system.NewAuthorityBtnUsecase,
|
||||
system.NewMenuUsecase,
|
||||
system.NewDictionaryUsecase,
|
||||
system.NewJwtBlacklistUsecase,
|
||||
system.NewOperationRecordUsecase,
|
||||
system.NewParamsUsecase,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
// Api API实体
|
||||
|
|
@ -71,12 +73,12 @@ type RouterInfo struct {
|
|||
}
|
||||
|
||||
// NewApiUsecase 创建API用例
|
||||
func NewApiUsecase(repo ApiRepo, casbinRepo CasbinRepo, authRepo AuthorityRepo, useStrictAuth bool) *ApiUsecase {
|
||||
func NewApiUsecase(repo ApiRepo, casbinRepo CasbinRepo, authRepo AuthorityRepo, useStrictAuth conf.UseStrictAuth) *ApiUsecase {
|
||||
return &ApiUsecase{
|
||||
repo: repo,
|
||||
casbinRepo: casbinRepo,
|
||||
authRepo: authRepo,
|
||||
useStrictAuth: useStrictAuth,
|
||||
useStrictAuth: bool(useStrictAuth),
|
||||
routers: make([]RouterInfo, 0),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -84,11 +86,11 @@ type AuthorityUsecase struct {
|
|||
}
|
||||
|
||||
// NewAuthorityUsecase 创建角色用例
|
||||
func NewAuthorityUsecase(repo AuthorityRepo, casbinRepo CasbinRepo, useStrictAuth bool) *AuthorityUsecase {
|
||||
func NewAuthorityUsecase(repo AuthorityRepo, casbinRepo CasbinRepo, useStrictAuth conf.UseStrictAuth) *AuthorityUsecase {
|
||||
return &AuthorityUsecase{
|
||||
repo: repo,
|
||||
casbinRepo: casbinRepo,
|
||||
useStrictAuth: useStrictAuth,
|
||||
useStrictAuth: bool(useStrictAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"kra/internal/conf"
|
||||
)
|
||||
|
||||
// BaseMenu 基础菜单实体
|
||||
|
|
@ -84,11 +86,11 @@ type MenuUsecase struct {
|
|||
}
|
||||
|
||||
// NewMenuUsecase 创建菜单用例
|
||||
func NewMenuUsecase(repo MenuRepo, authRepo AuthorityRepo, useStrictAuth bool) *MenuUsecase {
|
||||
func NewMenuUsecase(repo MenuRepo, authRepo AuthorityRepo, useStrictAuth conf.UseStrictAuth) *MenuUsecase {
|
||||
return &MenuUsecase{
|
||||
repo: repo,
|
||||
authRepo: authRepo,
|
||||
useStrictAuth: useStrictAuth,
|
||||
useStrictAuth: bool(useStrictAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Params 系统参数实体
|
||||
type Params struct {
|
||||
ID uint
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string // 参数名
|
||||
Key string // 参数键
|
||||
Value string // 参数值
|
||||
Desc string // 参数描述
|
||||
}
|
||||
|
||||
// ParamsRepo 系统参数仓储接口
|
||||
type ParamsRepo interface {
|
||||
Create(ctx context.Context, params *Params) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
DeleteByIds(ctx context.Context, ids []uint) error
|
||||
Update(ctx context.Context, params *Params) error
|
||||
FindByID(ctx context.Context, id uint) (*Params, error)
|
||||
FindByKey(ctx context.Context, key string) (*Params, error)
|
||||
FindList(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*Params, int64, error)
|
||||
}
|
||||
|
||||
// ParamsUsecase 系统参数用例
|
||||
type ParamsUsecase struct {
|
||||
repo ParamsRepo
|
||||
}
|
||||
|
||||
// NewParamsUsecase 创建系统参数用例
|
||||
func NewParamsUsecase(repo ParamsRepo) *ParamsUsecase {
|
||||
return &ParamsUsecase{repo: repo}
|
||||
}
|
||||
|
||||
// Create 创建参数
|
||||
func (uc *ParamsUsecase) Create(ctx context.Context, params *Params) error {
|
||||
return uc.repo.Create(ctx, params)
|
||||
}
|
||||
|
||||
// Delete 删除参数
|
||||
func (uc *ParamsUsecase) Delete(ctx context.Context, id uint) error {
|
||||
return uc.repo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteByIds 批量删除参数
|
||||
func (uc *ParamsUsecase) DeleteByIds(ctx context.Context, ids []uint) error {
|
||||
return uc.repo.DeleteByIds(ctx, ids)
|
||||
}
|
||||
|
||||
// Update 更新参数
|
||||
func (uc *ParamsUsecase) Update(ctx context.Context, params *Params) error {
|
||||
return uc.repo.Update(ctx, params)
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取参数
|
||||
func (uc *ParamsUsecase) GetByID(ctx context.Context, id uint) (*Params, error) {
|
||||
return uc.repo.FindByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetByKey 根据Key获取参数
|
||||
func (uc *ParamsUsecase) GetByKey(ctx context.Context, key string) (*Params, error) {
|
||||
return uc.repo.FindByKey(ctx, key)
|
||||
}
|
||||
|
||||
// GetList 获取参数列表
|
||||
func (uc *ParamsUsecase) GetList(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*Params, int64, error) {
|
||||
return uc.repo.FindList(ctx, page, pageSize, filters)
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package conf
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSet is conf providers.
|
||||
var ProviderSet = wire.NewSet(
|
||||
ProvideServer,
|
||||
ProvideJWT,
|
||||
ProvideMysql,
|
||||
ProvideCaptcha,
|
||||
ProvideSystem,
|
||||
ProvideUseStrictAuth,
|
||||
)
|
||||
|
||||
// ProvideServer 提供Server配置
|
||||
func ProvideServer(c *Bootstrap) *Server {
|
||||
return c.Server
|
||||
}
|
||||
|
||||
// ProvideJWT 提供JWT配置
|
||||
func ProvideJWT(c *Bootstrap) *JWT {
|
||||
return c.Jwt
|
||||
}
|
||||
|
||||
// ProvideMysql 提供Mysql配置
|
||||
func ProvideMysql(c *Bootstrap) *Mysql {
|
||||
return c.Mysql
|
||||
}
|
||||
|
||||
// ProvideCaptcha 提供Captcha配置
|
||||
func ProvideCaptcha(c *Bootstrap) *Captcha {
|
||||
return c.Captcha
|
||||
}
|
||||
|
||||
// ProvideSystem 提供System配置
|
||||
func ProvideSystem(c *Bootstrap) *System {
|
||||
return c.System
|
||||
}
|
||||
|
||||
// UseStrictAuth 用于wire注入的类型包装
|
||||
type UseStrictAuth bool
|
||||
|
||||
// ProvideUseStrictAuth 提供UseStrictAuth配置
|
||||
func ProvideUseStrictAuth(c *System) UseStrictAuth {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return UseStrictAuth(c.UseStrictAuth)
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ import (
|
|||
"fmt"
|
||||
"kra/internal/conf"
|
||||
datasystem "kra/internal/data/system"
|
||||
pkgcasbin "kra/pkg/casbin"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/google/wire"
|
||||
"gorm.io/driver/mysql"
|
||||
|
|
@ -16,8 +18,18 @@ import (
|
|||
var ProviderSet = wire.NewSet(
|
||||
NewDB,
|
||||
NewData,
|
||||
NewCasbinEnforcer,
|
||||
datasystem.NewUserRepo,
|
||||
datasystem.NewApiRepo,
|
||||
datasystem.NewAuthorityRepo,
|
||||
datasystem.NewAuthorityBtnRepo,
|
||||
datasystem.NewCasbinRepo,
|
||||
datasystem.NewMenuRepo,
|
||||
datasystem.NewDictionaryRepo,
|
||||
datasystem.NewDictionaryDetailRepo,
|
||||
datasystem.NewJwtBlacklistRepo,
|
||||
datasystem.NewOperationRecordRepo,
|
||||
datasystem.NewParamsRepo,
|
||||
)
|
||||
|
||||
// Data 数据层包装器
|
||||
|
|
@ -49,3 +61,8 @@ func NewDB(c *conf.Mysql) (*gorm.DB, error) {
|
|||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// NewCasbinEnforcer 创建Casbin Enforcer
|
||||
func NewCasbinEnforcer(db *gorm.DB) (*casbin.SyncedCachedEnforcer, error) {
|
||||
return pkgcasbin.InitCasbin(db, "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// NewGormDB 创建GORM数据库连接
|
||||
func NewGormDB(c *conf.Data, l log.Logger) (*gorm.DB, func(), error) {
|
||||
helper := log.NewHelper(l)
|
||||
|
||||
var dialector gorm.Dialector
|
||||
switch c.Database.Driver {
|
||||
case "mysql":
|
||||
dialector = mysql.Open(c.Database.Source)
|
||||
case "postgres":
|
||||
dialector = postgres.Open(c.Database.Source)
|
||||
case "sqlite":
|
||||
dialector = sqlite.Open(c.Database.Source)
|
||||
default:
|
||||
dialector = mysql.Open(c.Database.Source)
|
||||
}
|
||||
|
||||
gormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: c.Database.TablePrefix,
|
||||
SingularTable: true,
|
||||
},
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
}
|
||||
|
||||
// 设置日志级别
|
||||
if c.Database.LogMode {
|
||||
gormConfig.Logger = logger.Default.LogMode(logger.Info)
|
||||
} else {
|
||||
gormConfig.Logger = logger.Default.LogMode(logger.Silent)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(dialector, gormConfig)
|
||||
if err != nil {
|
||||
helper.Errorf("failed to connect database: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
helper.Errorf("failed to get sql.DB: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 连接池配置
|
||||
sqlDB.SetMaxIdleConns(int(c.Database.MaxIdleConns))
|
||||
sqlDB.SetMaxOpenConns(int(c.Database.MaxOpenConns))
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(c.Database.MaxLifetime) * time.Second)
|
||||
|
||||
cleanup := func() {
|
||||
helper.Info("closing database connection")
|
||||
if err := sqlDB.Close(); err != nil {
|
||||
helper.Errorf("failed to close database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
helper.Info("database connected successfully")
|
||||
return db, cleanup, nil
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// NewRedisClient 创建Redis客户端
|
||||
func NewRedisClient(c *conf.Data, l log.Logger) (redis.UniversalClient, func(), error) {
|
||||
helper := log.NewHelper(l)
|
||||
|
||||
var client redis.UniversalClient
|
||||
|
||||
if c.Redis.UseCluster {
|
||||
client = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: c.Redis.ClusterAddrs,
|
||||
Password: c.Redis.Password,
|
||||
})
|
||||
} else {
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: c.Redis.Addr,
|
||||
Password: c.Redis.Password,
|
||||
DB: int(c.Redis.Db),
|
||||
})
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
ctx := context.Background()
|
||||
if _, err := client.Ping(ctx).Result(); err != nil {
|
||||
helper.Errorf("failed to connect redis: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
helper.Info("closing redis connection")
|
||||
if err := client.Close(); err != nil {
|
||||
helper.Errorf("failed to close redis: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
helper.Info("redis connected successfully")
|
||||
return client, cleanup, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/data/model"
|
||||
"kra/internal/data/query"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ParamsRepo 系统参数仓储实现
|
||||
type ParamsRepo struct {
|
||||
data *gorm.DB
|
||||
}
|
||||
|
||||
// NewParamsRepo 创建系统参数仓储
|
||||
func NewParamsRepo(data *gorm.DB) system.ParamsRepo {
|
||||
return &ParamsRepo{data: data}
|
||||
}
|
||||
|
||||
// Create 创建参数
|
||||
func (r *ParamsRepo) Create(ctx context.Context, params *system.Params) error {
|
||||
m := toParamsModel(params)
|
||||
return query.Use(r.data).SysParam.WithContext(ctx).Create(m)
|
||||
}
|
||||
|
||||
// Delete 删除参数
|
||||
func (r *ParamsRepo) Delete(ctx context.Context, id uint) error {
|
||||
q := query.Use(r.data).SysParam
|
||||
_, err := q.WithContext(ctx).Where(q.ID.Eq(int64(id))).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteByIds 批量删除参数
|
||||
func (r *ParamsRepo) DeleteByIds(ctx context.Context, ids []uint) error {
|
||||
q := query.Use(r.data).SysParam
|
||||
int64Ids := make([]int64, len(ids))
|
||||
for i, id := range ids {
|
||||
int64Ids[i] = int64(id)
|
||||
}
|
||||
_, err := q.WithContext(ctx).Where(q.ID.In(int64Ids...)).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// Update 更新参数
|
||||
func (r *ParamsRepo) Update(ctx context.Context, params *system.Params) error {
|
||||
q := query.Use(r.data).SysParam
|
||||
_, err := q.WithContext(ctx).Where(q.ID.Eq(int64(params.ID))).Updates(toParamsModel(params))
|
||||
return err
|
||||
}
|
||||
|
||||
// FindByID 根据ID查找参数
|
||||
func (r *ParamsRepo) FindByID(ctx context.Context, id uint) (*system.Params, error) {
|
||||
q := query.Use(r.data).SysParam
|
||||
m, err := q.WithContext(ctx).Where(q.ID.Eq(int64(id))).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toParamsBiz(m), nil
|
||||
}
|
||||
|
||||
// FindByKey 根据Key查找参数
|
||||
func (r *ParamsRepo) FindByKey(ctx context.Context, key string) (*system.Params, error) {
|
||||
q := query.Use(r.data).SysParam
|
||||
m, err := q.WithContext(ctx).Where(q.Key.Eq(key)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toParamsBiz(m), nil
|
||||
}
|
||||
|
||||
// FindList 查找参数列表
|
||||
func (r *ParamsRepo) FindList(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*system.Params, int64, error) {
|
||||
q := query.Use(r.data).SysParam
|
||||
do := q.WithContext(ctx)
|
||||
|
||||
// 应用过滤条件
|
||||
if name, ok := filters["name"].(string); ok && name != "" {
|
||||
do = do.Where(q.Name.Like("%" + name + "%"))
|
||||
}
|
||||
if key, ok := filters["key"].(string); ok && key != "" {
|
||||
do = do.Where(q.Key.Like("%" + key + "%"))
|
||||
}
|
||||
if startTime, ok := filters["start_time"].(*time.Time); ok && startTime != nil {
|
||||
do = do.Where(q.CreatedAt.Gte(*startTime))
|
||||
}
|
||||
if endTime, ok := filters["end_time"].(*time.Time); ok && endTime != nil {
|
||||
do = do.Where(q.CreatedAt.Lte(*endTime))
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total, err := do.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
if pageSize > 0 {
|
||||
do = do.Limit(pageSize).Offset((page - 1) * pageSize)
|
||||
}
|
||||
|
||||
list, err := do.Find()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
result := make([]*system.Params, len(list))
|
||||
for i, m := range list {
|
||||
result[i] = toParamsBiz(m)
|
||||
}
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// toParamsModel 转换为数据模型
|
||||
func toParamsModel(p *system.Params) *model.SysParam {
|
||||
return &model.SysParam{
|
||||
ID: int64(p.ID),
|
||||
Name: p.Name,
|
||||
Key: p.Key,
|
||||
Value: p.Value,
|
||||
Desc: p.Desc,
|
||||
}
|
||||
}
|
||||
|
||||
// toParamsBiz 转换为业务实体
|
||||
func toParamsBiz(m *model.SysParam) *system.Params {
|
||||
return &system.Params{
|
||||
ID: uint(m.ID),
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
Name: m.Name,
|
||||
Key: m.Key,
|
||||
Value: m.Value,
|
||||
Desc: m.Desc,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,93 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/middleware"
|
||||
"kra/internal/service/system"
|
||||
"kra/pkg/jwt"
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/middleware/recovery"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
"github.com/go-kratos/kratos/v2/middleware/selector"
|
||||
khttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// NewHTTPServer new an HTTP server.
|
||||
func NewHTTPServer(c *conf.Server) *http.Server {
|
||||
var opts = []http.ServerOption{
|
||||
http.Middleware(
|
||||
func NewHTTPServer(
|
||||
c *conf.Server,
|
||||
jwtPkg *jwt.JWT,
|
||||
userSvc *system.UserService,
|
||||
apiSvc *system.ApiService,
|
||||
authoritySvc *system.AuthorityService,
|
||||
authorityBtnSvc *system.AuthorityBtnService,
|
||||
captchaSvc *system.CaptchaService,
|
||||
casbinSvc *system.CasbinService,
|
||||
menuSvc *system.MenuService,
|
||||
dictionarySvc *system.DictionaryService,
|
||||
jwtBlacklistSvc *system.JwtBlacklistService,
|
||||
operationRecordSvc *system.OperationRecordService,
|
||||
paramsSvc *system.ParamsService,
|
||||
systemConfigSvc *system.SystemConfigService,
|
||||
) *khttp.Server {
|
||||
var opts = []khttp.ServerOption{
|
||||
khttp.Middleware(
|
||||
recovery.Recovery(),
|
||||
// JWT认证中间件(排除公开路由)
|
||||
selector.Server(
|
||||
middleware.JWTAuthSimple(jwtPkg),
|
||||
).Match(newWhiteListMatcher()).Build(),
|
||||
),
|
||||
khttp.ResponseEncoder(response.Encoder),
|
||||
khttp.ErrorEncoder(response.ErrorEncoder),
|
||||
}
|
||||
if c.Http.Network != "" {
|
||||
opts = append(opts, http.Network(c.Http.Network))
|
||||
opts = append(opts, khttp.Network(c.Http.Network))
|
||||
}
|
||||
if c.Http.Addr != "" {
|
||||
opts = append(opts, http.Address(c.Http.Addr))
|
||||
opts = append(opts, khttp.Address(c.Http.Addr))
|
||||
}
|
||||
if c.Http.Timeout != nil {
|
||||
opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration()))
|
||||
opts = append(opts, khttp.Timeout(c.Http.Timeout.AsDuration()))
|
||||
}
|
||||
srv := http.NewServer(opts...)
|
||||
srv := khttp.NewServer(opts...)
|
||||
|
||||
// 创建路由组
|
||||
rg := &system.RouterGroup{
|
||||
Public: srv.Route("/"),
|
||||
Private: srv.Route("/"),
|
||||
PrivateWithRecord: srv.Route("/"),
|
||||
}
|
||||
|
||||
// 注册各模块路由
|
||||
userSvc.RegisterRoutes(rg)
|
||||
apiSvc.RegisterRoutes(rg)
|
||||
authoritySvc.RegisterRoutes(rg)
|
||||
authorityBtnSvc.RegisterRoutes(rg)
|
||||
captchaSvc.RegisterRoutes(rg)
|
||||
casbinSvc.RegisterRoutes(rg)
|
||||
menuSvc.RegisterRoutes(rg)
|
||||
dictionarySvc.RegisterRoutes(rg)
|
||||
jwtBlacklistSvc.RegisterRoutes(rg)
|
||||
operationRecordSvc.RegisterRoutes(rg)
|
||||
paramsSvc.RegisterRoutes(rg)
|
||||
systemConfigSvc.RegisterRoutes(rg)
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
// newWhiteListMatcher 创建白名单匹配器(这些路由不需要JWT认证)
|
||||
func newWhiteListMatcher() selector.MatchFunc {
|
||||
whiteList := map[string]struct{}{
|
||||
"/base/login": {},
|
||||
"/base/captcha": {},
|
||||
"/api/freshCasbin": {},
|
||||
}
|
||||
return func(ctx context.Context, operation string) bool {
|
||||
if _, ok := whiteList[operation]; ok {
|
||||
return false // 不需要认证
|
||||
}
|
||||
return true // 需要认证
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,23 @@
|
|||
package service
|
||||
|
||||
import "github.com/google/wire"
|
||||
import (
|
||||
"kra/internal/service/system"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// ProviderSet is service providers.
|
||||
var ProviderSet = wire.NewSet()
|
||||
var ProviderSet = wire.NewSet(
|
||||
system.NewUserService,
|
||||
system.NewApiService,
|
||||
system.NewAuthorityService,
|
||||
system.NewAuthorityBtnService,
|
||||
system.NewCaptchaService,
|
||||
system.NewCasbinService,
|
||||
system.NewMenuService,
|
||||
system.NewDictionaryService,
|
||||
system.NewJwtBlacklistService,
|
||||
system.NewOperationRecordService,
|
||||
system.NewParamsService,
|
||||
system.NewSystemConfigService,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/server/middleware"
|
||||
types "kra/internal/service/types/system"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
khttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// ApiService API服务
|
||||
|
|
@ -20,45 +19,30 @@ func NewApiService(uc *system.ApiUsecase) *ApiService {
|
|||
return &ApiService{uc: uc}
|
||||
}
|
||||
|
||||
// ApiInfo API信息
|
||||
type ApiInfo struct {
|
||||
ID uint `json:"ID"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// CreateApiRequest 创建API请求
|
||||
type CreateApiRequest struct {
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// CreateApi 创建API
|
||||
func (s *ApiService) CreateApi(ctx context.Context, req *CreateApiRequest) error {
|
||||
func (s *ApiService) CreateApi(ctx khttp.Context) error {
|
||||
var req types.CreateApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
api := &system.Api{
|
||||
Path: req.Path,
|
||||
Description: req.Description,
|
||||
ApiGroup: req.ApiGroup,
|
||||
Method: req.Method,
|
||||
}
|
||||
return s.uc.CreateApi(ctx, api)
|
||||
}
|
||||
|
||||
// UpdateApiRequest 更新API请求
|
||||
type UpdateApiRequest struct {
|
||||
ID uint `json:"ID"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
if err := s.uc.CreateApi(ctx, api); err != nil {
|
||||
return errors.BadRequest("CREATE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// UpdateApi 更新API
|
||||
func (s *ApiService) UpdateApi(ctx context.Context, req *UpdateApiRequest) error {
|
||||
func (s *ApiService) UpdateApi(ctx khttp.Context) error {
|
||||
var req types.UpdateApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
api := &system.Api{
|
||||
ID: req.ID,
|
||||
Path: req.Path,
|
||||
|
|
@ -66,55 +50,55 @@ func (s *ApiService) UpdateApi(ctx context.Context, req *UpdateApiRequest) error
|
|||
ApiGroup: req.ApiGroup,
|
||||
Method: req.Method,
|
||||
}
|
||||
return s.uc.UpdateApi(ctx, api)
|
||||
if err := s.uc.UpdateApi(ctx, api); err != nil {
|
||||
return errors.BadRequest("UPDATE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// DeleteApi 删除API
|
||||
func (s *ApiService) DeleteApi(ctx context.Context, id uint) error {
|
||||
return s.uc.DeleteApi(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteApisByIdsRequest 批量删除API请求
|
||||
type DeleteApisByIdsRequest struct {
|
||||
Ids []uint `json:"ids"`
|
||||
func (s *ApiService) DeleteApi(ctx khttp.Context) error {
|
||||
var req types.DeleteApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uc.DeleteApi(ctx, req.ID); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// DeleteApisByIds 批量删除API
|
||||
func (s *ApiService) DeleteApisByIds(ctx context.Context, req *DeleteApisByIdsRequest) error {
|
||||
return s.uc.DeleteApisByIds(ctx, req.Ids)
|
||||
func (s *ApiService) DeleteApisByIds(ctx khttp.Context) error {
|
||||
var req types.DeleteApisByIdsRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uc.DeleteApisByIds(ctx, req.Ids); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// GetApiById 根据ID获取API
|
||||
func (s *ApiService) GetApiById(ctx context.Context, id uint) (*ApiInfo, error) {
|
||||
api, err := s.uc.GetApiById(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *ApiService) GetApiById(ctx khttp.Context) error {
|
||||
var req types.GetApiByIdRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
return toApiInfo(api), nil
|
||||
}
|
||||
|
||||
// GetApiListRequest 获取API列表请求
|
||||
type GetApiListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
Method string `json:"method"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
OrderKey string `json:"orderKey"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
||||
// GetApiListResponse 获取API列表响应
|
||||
type GetApiListResponse struct {
|
||||
List []*ApiInfo `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
api, err := s.uc.GetApiById(ctx, req.ID)
|
||||
if err != nil {
|
||||
return errors.NotFound("NOT_FOUND", err.Error())
|
||||
}
|
||||
return ctx.Result(200, &types.GetApiByIdReply{Api: toTypesApiInfo(api)})
|
||||
}
|
||||
|
||||
// GetApiList 获取API列表
|
||||
func (s *ApiService) GetApiList(ctx context.Context, req *GetApiListRequest) (*GetApiListResponse, error) {
|
||||
func (s *ApiService) GetApiList(ctx khttp.Context) error {
|
||||
var req types.GetApiListRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
filters := make(map[string]string)
|
||||
if req.Path != "" {
|
||||
filters["path"] = req.Path
|
||||
|
|
@ -128,94 +112,71 @@ func (s *ApiService) GetApiList(ctx context.Context, req *GetApiListRequest) (*G
|
|||
if req.ApiGroup != "" {
|
||||
filters["api_group"] = req.ApiGroup
|
||||
}
|
||||
|
||||
apis, total, err := s.uc.GetAPIInfoList(ctx, req.Page, req.PageSize, filters, req.OrderKey, req.Desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
|
||||
list := make([]*ApiInfo, len(apis))
|
||||
list := make([]types.ApiInfo, len(apis))
|
||||
for i, api := range apis {
|
||||
list[i] = toApiInfo(api)
|
||||
list[i] = toTypesApiInfo(api)
|
||||
}
|
||||
|
||||
return &GetApiListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
return ctx.Result(200, &types.GetApiListReply{List: list, Total: total, Page: req.Page, PageSize: req.PageSize})
|
||||
}
|
||||
|
||||
// GetAllApis 获取所有API
|
||||
func (s *ApiService) GetAllApis(ctx context.Context, authorityID uint) ([]*ApiInfo, error) {
|
||||
func (s *ApiService) GetAllApis(ctx khttp.Context) error {
|
||||
authorityID := middleware.GetAuthorityID(ctx)
|
||||
apis, err := s.uc.GetAllApis(ctx, authorityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
list := make([]*ApiInfo, len(apis))
|
||||
list := make([]types.ApiInfo, len(apis))
|
||||
for i, api := range apis {
|
||||
list[i] = toApiInfo(api)
|
||||
list[i] = toTypesApiInfo(api)
|
||||
}
|
||||
return list, nil
|
||||
return ctx.Result(200, &types.GetAllApisReply{Apis: list})
|
||||
}
|
||||
|
||||
// GetApiGroups 获取API分组
|
||||
func (s *ApiService) GetApiGroups(ctx context.Context) ([]string, map[string]string, error) {
|
||||
return s.uc.GetApiGroups(ctx)
|
||||
}
|
||||
|
||||
// SyncApiResponse 同步API响应
|
||||
type SyncApiResponse struct {
|
||||
NewApis []*ApiInfo `json:"newApis"`
|
||||
DeleteApis []*ApiInfo `json:"deleteApis"`
|
||||
IgnoreApis []*ApiInfo `json:"ignoreApis"`
|
||||
func (s *ApiService) GetApiGroups(ctx khttp.Context) error {
|
||||
groups, apiGroupMap, err := s.uc.GetApiGroups(ctx)
|
||||
if err != nil {
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, &types.GetApiGroupsReply{Groups: groups, ApiGroupMap: apiGroupMap})
|
||||
}
|
||||
|
||||
// SyncApi 同步API
|
||||
func (s *ApiService) SyncApi(ctx context.Context) (*SyncApiResponse, error) {
|
||||
func (s *ApiService) SyncApi(ctx khttp.Context) error {
|
||||
newApis, deleteApis, ignoreApis, err := s.uc.SyncApi(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return errors.InternalServer("SYNC_ERROR", err.Error())
|
||||
}
|
||||
|
||||
resp := &SyncApiResponse{
|
||||
NewApis: make([]*ApiInfo, len(newApis)),
|
||||
DeleteApis: make([]*ApiInfo, len(deleteApis)),
|
||||
IgnoreApis: make([]*ApiInfo, len(ignoreApis)),
|
||||
}
|
||||
for i, api := range newApis {
|
||||
resp.NewApis[i] = toApiInfo(api)
|
||||
}
|
||||
for i, api := range deleteApis {
|
||||
resp.DeleteApis[i] = toApiInfo(api)
|
||||
}
|
||||
for i, api := range ignoreApis {
|
||||
resp.IgnoreApis[i] = toApiInfo(api)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// IgnoreApiRequest 忽略API请求
|
||||
type IgnoreApiRequest struct {
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
Flag bool `json:"flag"`
|
||||
return ctx.Result(200, &types.SyncApiReply{
|
||||
NewApis: toTypesApiInfoList(newApis),
|
||||
DeleteApis: toTypesApiInfoList(deleteApis),
|
||||
IgnoreApis: toTypesApiInfoList(ignoreApis),
|
||||
})
|
||||
}
|
||||
|
||||
// IgnoreApi 忽略API
|
||||
func (s *ApiService) IgnoreApi(ctx context.Context, req *IgnoreApiRequest) error {
|
||||
return s.uc.IgnoreApi(ctx, req.Path, req.Method, req.Flag)
|
||||
}
|
||||
|
||||
// EnterSyncApiRequest 确认同步API请求
|
||||
type EnterSyncApiRequest struct {
|
||||
NewApis []*ApiInfo `json:"newApis"`
|
||||
DeleteApis []*ApiInfo `json:"deleteApis"`
|
||||
func (s *ApiService) IgnoreApi(ctx khttp.Context) error {
|
||||
var req types.IgnoreApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uc.IgnoreApi(ctx, req.Path, req.Method, req.Flag); err != nil {
|
||||
return errors.BadRequest("IGNORE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// EnterSyncApi 确认同步API
|
||||
func (s *ApiService) EnterSyncApi(ctx context.Context, req *EnterSyncApiRequest) error {
|
||||
func (s *ApiService) EnterSyncApi(ctx khttp.Context) error {
|
||||
var req types.EnterSyncApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
newApis := make([]*system.Api, len(req.NewApis))
|
||||
for i, api := range req.NewApis {
|
||||
newApis[i] = &system.Api{
|
||||
|
|
@ -233,17 +194,26 @@ func (s *ApiService) EnterSyncApi(ctx context.Context, req *EnterSyncApiRequest)
|
|||
Method: api.Method,
|
||||
}
|
||||
}
|
||||
return s.uc.EnterSyncApi(ctx, newApis, deleteApis)
|
||||
if err := s.uc.EnterSyncApi(ctx, newApis, deleteApis); err != nil {
|
||||
return errors.BadRequest("SYNC_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// FreshCasbin 刷新Casbin缓存
|
||||
func (s *ApiService) FreshCasbin() error {
|
||||
return s.uc.FreshCasbin()
|
||||
func (s *ApiService) FreshCasbin(ctx khttp.Context) error {
|
||||
if err := s.uc.FreshCasbin(); err != nil {
|
||||
return errors.InternalServer("FRESH_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// 转换函数
|
||||
func toApiInfo(api *system.Api) *ApiInfo {
|
||||
return &ApiInfo{
|
||||
// toTypesApiInfo 转换API信息为types格式
|
||||
func toTypesApiInfo(api *system.Api) types.ApiInfo {
|
||||
if api == nil {
|
||||
return types.ApiInfo{}
|
||||
}
|
||||
return types.ApiInfo{
|
||||
ID: api.ID,
|
||||
Path: api.Path,
|
||||
Description: api.Description,
|
||||
|
|
@ -252,147 +222,32 @@ func toApiInfo(api *system.Api) *ApiInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *ApiService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
|
||||
r.POST("/api/createApi", s.handleCreateApi)
|
||||
r.POST("/api/updateApi", s.handleUpdateApi)
|
||||
r.POST("/api/deleteApi", s.handleDeleteApi)
|
||||
r.DELETE("/api/deleteApisByIds", s.handleDeleteApisByIds)
|
||||
r.POST("/api/getApiList", s.handleGetApiList)
|
||||
r.POST("/api/getApiById", s.handleGetApiById)
|
||||
r.POST("/api/getAllApis", s.handleGetAllApis)
|
||||
r.GET("/api/getApiGroups", s.handleGetApiGroups)
|
||||
r.GET("/api/syncApi", s.handleSyncApi)
|
||||
r.POST("/api/ignoreApi", s.handleIgnoreApi)
|
||||
r.POST("/api/enterSyncApi", s.handleEnterSyncApi)
|
||||
r.GET("/api/freshCasbin", s.handleFreshCasbin)
|
||||
// toTypesApiInfoList 批量转换API信息
|
||||
func toTypesApiInfoList(apis []*system.Api) []types.ApiInfo {
|
||||
list := make([]types.ApiInfo, len(apis))
|
||||
for i, api := range apis {
|
||||
list[i] = toTypesApiInfo(api)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// HTTP Handlers
|
||||
func (s *ApiService) handleCreateApi(ctx http.Context) error {
|
||||
var req CreateApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.CreateApi(ctx, &req); err != nil {
|
||||
return errors.BadRequest("CREATE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "创建成功"})
|
||||
}
|
||||
// RegisterRoutes 注册API管理路由
|
||||
func (s *ApiService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 公开路由
|
||||
rg.Public.GET("/api/freshCasbin", s.FreshCasbin)
|
||||
|
||||
func (s *ApiService) handleUpdateApi(ctx http.Context) error {
|
||||
var req UpdateApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.UpdateApi(ctx, &req); err != nil {
|
||||
return errors.BadRequest("UPDATE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "修改成功"})
|
||||
}
|
||||
// 私有路由 + 操作记录(写操作)
|
||||
rg.PrivateWithRecord.GET("/api/getApiGroups", s.GetApiGroups)
|
||||
rg.PrivateWithRecord.GET("/api/syncApi", s.SyncApi)
|
||||
rg.PrivateWithRecord.POST("/api/ignoreApi", s.IgnoreApi)
|
||||
rg.PrivateWithRecord.POST("/api/enterSyncApi", s.EnterSyncApi)
|
||||
rg.PrivateWithRecord.POST("/api/createApi", s.CreateApi)
|
||||
rg.PrivateWithRecord.POST("/api/deleteApi", s.DeleteApi)
|
||||
rg.PrivateWithRecord.POST("/api/getApiById", s.GetApiById)
|
||||
rg.PrivateWithRecord.POST("/api/updateApi", s.UpdateApi)
|
||||
rg.PrivateWithRecord.DELETE("/api/deleteApisByIds", s.DeleteApisByIds)
|
||||
|
||||
func (s *ApiService) handleDeleteApi(ctx http.Context) error {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.DeleteApi(ctx, req.ID); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleDeleteApisByIds(ctx http.Context) error {
|
||||
var req DeleteApisByIdsRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.DeleteApisByIds(ctx, &req); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleGetApiList(ctx http.Context) error {
|
||||
var req GetApiListRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.GetApiList(ctx, &req)
|
||||
if err != nil {
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": resp})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleGetApiById(ctx http.Context) error {
|
||||
var req struct {
|
||||
ID uint `json:"ID"`
|
||||
}
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.GetApiById(ctx, req.ID)
|
||||
if err != nil {
|
||||
return errors.NotFound("NOT_FOUND", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"api": resp}})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleGetAllApis(ctx http.Context) error {
|
||||
authorityID := middleware.GetAuthorityID(ctx)
|
||||
resp, err := s.GetAllApis(ctx, authorityID)
|
||||
if err != nil {
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"apis": resp}})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleGetApiGroups(ctx http.Context) error {
|
||||
groups, apiGroupMap, err := s.GetApiGroups(ctx)
|
||||
if err != nil {
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"groups": groups, "apiGroupMap": apiGroupMap}})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleSyncApi(ctx http.Context) error {
|
||||
resp, err := s.SyncApi(ctx)
|
||||
if err != nil {
|
||||
return errors.InternalServer("SYNC_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "data": resp})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleIgnoreApi(ctx http.Context) error {
|
||||
var req IgnoreApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.IgnoreApi(ctx, &req); err != nil {
|
||||
return errors.BadRequest("IGNORE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "操作成功"})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleEnterSyncApi(ctx http.Context) error {
|
||||
var req EnterSyncApiRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.EnterSyncApi(ctx, &req); err != nil {
|
||||
return errors.BadRequest("SYNC_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "同步成功"})
|
||||
}
|
||||
|
||||
func (s *ApiService) handleFreshCasbin(ctx http.Context) error {
|
||||
if err := s.FreshCasbin(); err != nil {
|
||||
return errors.InternalServer("FRESH_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "刷新成功"})
|
||||
// 私有路由(读操作,不记录)
|
||||
rg.Private.POST("/api/getAllApis", s.GetAllApis)
|
||||
rg.Private.POST("/api/getApiList", s.GetApiList)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/server/middleware"
|
||||
types "kra/internal/service/types/system"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
|
|
@ -26,12 +27,12 @@ func NewAuthorityService(uc *system.AuthorityUsecase, casbin system.CasbinRepo)
|
|||
|
||||
// AuthorityInfo 角色信息
|
||||
type AuthorityFullInfo struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityName string `json:"authorityName"`
|
||||
ParentId *uint `json:"parentId,omitempty"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
DataAuthorityId []*AuthorityInfo `json:"dataAuthorityId,omitempty"`
|
||||
Children []*AuthorityFullInfo `json:"children,omitempty"`
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityName string `json:"authorityName"`
|
||||
ParentId *uint `json:"parentId,omitempty"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
DataAuthorityId []*types.AuthorityInfo `json:"dataAuthorityId,omitempty"`
|
||||
Children []*AuthorityFullInfo `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// CreateAuthorityRequest 创建角色请求
|
||||
|
|
@ -172,9 +173,9 @@ func toAuthorityFullInfoList(list []*system.AuthorityFull) []*AuthorityFullInfo
|
|||
}
|
||||
// DataAuthorityId
|
||||
if len(auth.DataAuthorityId) > 0 {
|
||||
result[i].DataAuthorityId = make([]*AuthorityInfo, len(auth.DataAuthorityId))
|
||||
result[i].DataAuthorityId = make([]*types.AuthorityInfo, len(auth.DataAuthorityId))
|
||||
for j, da := range auth.DataAuthorityId {
|
||||
result[i].DataAuthorityId[j] = &AuthorityInfo{
|
||||
result[i].DataAuthorityId[j] = &types.AuthorityInfo{
|
||||
AuthorityId: da.AuthorityId,
|
||||
AuthorityName: da.AuthorityName,
|
||||
ParentId: da.ParentId,
|
||||
|
|
@ -187,15 +188,16 @@ func toAuthorityFullInfoList(list []*system.AuthorityFull) []*AuthorityFullInfo
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *AuthorityService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
func (s *AuthorityService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 私有路由 + 操作记录(写操作)
|
||||
rg.PrivateWithRecord.POST("/authority/createAuthority", s.handleCreateAuthority)
|
||||
rg.PrivateWithRecord.POST("/authority/copyAuthority", s.handleCopyAuthority)
|
||||
rg.PrivateWithRecord.PUT("/authority/updateAuthority", s.handleUpdateAuthority)
|
||||
rg.PrivateWithRecord.POST("/authority/deleteAuthority", s.handleDeleteAuthority)
|
||||
rg.PrivateWithRecord.POST("/authority/setDataAuthority", s.handleSetDataAuthority)
|
||||
|
||||
r.POST("/authority/createAuthority", s.handleCreateAuthority)
|
||||
r.POST("/authority/copyAuthority", s.handleCopyAuthority)
|
||||
r.PUT("/authority/updateAuthority", s.handleUpdateAuthority)
|
||||
r.POST("/authority/deleteAuthority", s.handleDeleteAuthority)
|
||||
r.POST("/authority/getAuthorityList", s.handleGetAuthorityList)
|
||||
r.POST("/authority/setDataAuthority", s.handleSetDataAuthority)
|
||||
// 私有路由(读操作,不记录)
|
||||
rg.Private.POST("/authority/getAuthorityList", s.handleGetAuthorityList)
|
||||
}
|
||||
|
||||
// HTTP Handlers
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ type SetAuthorityBtnRequest struct {
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *AuthorityBtnService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/authorityBtn/getAuthorityBtn", s.handleGetAuthorityBtn)
|
||||
r.POST("/authorityBtn/setAuthorityBtn", s.handleSetAuthorityBtn)
|
||||
r.POST("/authorityBtn/canRemoveAuthorityBtn", s.handleCanRemoveAuthorityBtn)
|
||||
func (s *AuthorityBtnService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 角色按钮权限(不需要操作记录)
|
||||
rg.Private.POST("/authorityBtn/getAuthorityBtn", s.handleGetAuthorityBtn)
|
||||
rg.Private.POST("/authorityBtn/setAuthorityBtn", s.handleSetAuthorityBtn)
|
||||
rg.Private.POST("/authorityBtn/canRemoveAuthorityBtn", s.handleCanRemoveAuthorityBtn)
|
||||
}
|
||||
|
||||
func (s *AuthorityBtnService) handleGetAuthorityBtn(ctx http.Context) error {
|
||||
|
|
|
|||
|
|
@ -4,24 +4,30 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
// CaptchaService 验证码服务
|
||||
type CaptchaService struct {
|
||||
config CaptchaConfig
|
||||
store base64Captcha.Store
|
||||
blackCache *BlackCache
|
||||
keyLong int
|
||||
imgWidth int
|
||||
imgHeight int
|
||||
openCaptcha int // 防爆次数,0表示关闭
|
||||
openCaptchaTimeOut int // 缓存超时时间(秒)
|
||||
store base64Captcha.Store
|
||||
blackCache *BlackCache
|
||||
}
|
||||
|
||||
// CaptchaConfig 验证码配置
|
||||
// CaptchaConfig 验证码配置(保留用于兼容)
|
||||
type CaptchaConfig struct {
|
||||
KeyLong int
|
||||
ImgWidth int
|
||||
ImgHeight int
|
||||
OpenCaptcha int // 防爆次数,0表示关闭
|
||||
OpenCaptchaTimeOut int // 缓存超时时间(秒)
|
||||
OpenCaptcha int
|
||||
OpenCaptchaTimeOut int
|
||||
}
|
||||
|
||||
// BlackCache 黑名单缓存
|
||||
|
|
@ -86,21 +92,22 @@ type CaptchaResponse struct {
|
|||
}
|
||||
|
||||
// NewCaptchaService 创建验证码服务
|
||||
func NewCaptchaService(config CaptchaConfig, store base64Captcha.Store) *CaptchaService {
|
||||
if store == nil {
|
||||
store = base64Captcha.DefaultMemStore
|
||||
}
|
||||
func NewCaptchaService(c *conf.Captcha) *CaptchaService {
|
||||
return &CaptchaService{
|
||||
config: config,
|
||||
store: store,
|
||||
blackCache: NewBlackCache(),
|
||||
keyLong: int(c.KeyLong),
|
||||
imgWidth: int(c.ImgWidth),
|
||||
imgHeight: int(c.ImgHeight),
|
||||
openCaptcha: int(c.OpenCaptcha),
|
||||
openCaptchaTimeOut: int(c.OpenCaptchaTimeout),
|
||||
store: base64Captcha.DefaultMemStore,
|
||||
blackCache: NewBlackCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *CaptchaService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/base/captcha", s.handleCaptcha)
|
||||
func (s *CaptchaService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 验证码(公开路由,无需认证)
|
||||
rg.Public.POST("/base/captcha", s.handleCaptcha)
|
||||
}
|
||||
|
||||
func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||
|
|
@ -108,8 +115,8 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
|||
clientIP := ctx.Request().RemoteAddr
|
||||
|
||||
// 判断验证码是否开启
|
||||
openCaptcha := s.config.OpenCaptcha
|
||||
openCaptchaTimeOut := s.config.OpenCaptchaTimeOut
|
||||
openCaptcha := s.openCaptcha
|
||||
openCaptchaTimeOut := s.openCaptchaTimeOut
|
||||
|
||||
v, ok := s.blackCache.Get(clientIP)
|
||||
if !ok {
|
||||
|
|
@ -123,9 +130,9 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
|||
|
||||
// 生成验证码
|
||||
driver := base64Captcha.NewDriverDigit(
|
||||
s.config.ImgHeight,
|
||||
s.config.ImgWidth,
|
||||
s.config.KeyLong,
|
||||
s.imgHeight,
|
||||
s.imgWidth,
|
||||
s.keyLong,
|
||||
0.7,
|
||||
80,
|
||||
)
|
||||
|
|
@ -144,7 +151,7 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
|||
"data": CaptchaResponse{
|
||||
CaptchaId: id,
|
||||
PicPath: b64s,
|
||||
CaptchaLength: s.config.KeyLong,
|
||||
CaptchaLength: s.keyLong,
|
||||
OpenCaptcha: oc,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
kerrors "github.com/go-kratos/kratos/v2/errors"
|
||||
|
|
@ -20,12 +21,12 @@ type CasbinService struct {
|
|||
}
|
||||
|
||||
// NewCasbinService 创建Casbin服务
|
||||
func NewCasbinService(casbinRepo system.CasbinRepo, authUc *system.AuthorityUsecase, apiUc *system.ApiUsecase, useStrictAuth bool) *CasbinService {
|
||||
func NewCasbinService(casbinRepo system.CasbinRepo, authUc *system.AuthorityUsecase, apiUc *system.ApiUsecase, useStrictAuth conf.UseStrictAuth) *CasbinService {
|
||||
return &CasbinService{
|
||||
casbinRepo: casbinRepo,
|
||||
authUc: authUc,
|
||||
apiUc: apiUc,
|
||||
useStrictAuth: useStrictAuth,
|
||||
useStrictAuth: bool(useStrictAuth),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,10 +82,12 @@ func (s *CasbinService) GetPolicyPathByAuthorityId(authorityId uint) []system.Ca
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *CasbinService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/casbin/UpdateCasbin", s.handleUpdateCasbin)
|
||||
r.POST("/casbin/getPolicyPathByAuthorityId", s.handleGetPolicyPath)
|
||||
func (s *CasbinService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 私有路由 + 操作记录(写操作)
|
||||
rg.PrivateWithRecord.POST("/casbin/updateCasbin", s.handleUpdateCasbin)
|
||||
|
||||
// 私有路由(读操作,不记录)
|
||||
rg.Private.POST("/casbin/getPolicyPathByAuthorityId", s.handleGetPolicyPath)
|
||||
}
|
||||
|
||||
func (s *CasbinService) handleUpdateCasbin(ctx http.Context) error {
|
||||
|
|
|
|||
|
|
@ -365,28 +365,30 @@ func toDictionaryDetailInfoList(list []*system.DictionaryDetail) []*DictionaryDe
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *DictionaryService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
func (s *DictionaryService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 字典接口 - 写操作(带操作记录)
|
||||
rg.PrivateWithRecord.POST("/sysDictionary/createSysDictionary", s.handleCreateDictionary)
|
||||
rg.PrivateWithRecord.DELETE("/sysDictionary/deleteSysDictionary", s.handleDeleteDictionary)
|
||||
rg.PrivateWithRecord.PUT("/sysDictionary/updateSysDictionary", s.handleUpdateDictionary)
|
||||
rg.PrivateWithRecord.POST("/sysDictionary/importSysDictionary", s.handleImportDictionary)
|
||||
rg.PrivateWithRecord.GET("/sysDictionary/exportSysDictionary", s.handleExportDictionary)
|
||||
|
||||
// 字典接口
|
||||
r.POST("/sysDictionary/createSysDictionary", s.handleCreateDictionary)
|
||||
r.DELETE("/sysDictionary/deleteSysDictionary", s.handleDeleteDictionary)
|
||||
r.PUT("/sysDictionary/updateSysDictionary", s.handleUpdateDictionary)
|
||||
r.GET("/sysDictionary/findSysDictionary", s.handleGetDictionary)
|
||||
r.GET("/sysDictionary/getSysDictionaryList", s.handleGetDictionaryList)
|
||||
r.GET("/sysDictionary/exportSysDictionary", s.handleExportDictionary)
|
||||
r.POST("/sysDictionary/importSysDictionary", s.handleImportDictionary)
|
||||
// 字典接口 - 读操作(不记录)
|
||||
rg.Private.GET("/sysDictionary/findSysDictionary", s.handleGetDictionary)
|
||||
rg.Private.GET("/sysDictionary/getSysDictionaryList", s.handleGetDictionaryList)
|
||||
|
||||
// 字典详情接口
|
||||
r.POST("/sysDictionaryDetail/createSysDictionaryDetail", s.handleCreateDictionaryDetail)
|
||||
r.DELETE("/sysDictionaryDetail/deleteSysDictionaryDetail", s.handleDeleteDictionaryDetail)
|
||||
r.PUT("/sysDictionaryDetail/updateSysDictionaryDetail", s.handleUpdateDictionaryDetail)
|
||||
r.GET("/sysDictionaryDetail/findSysDictionaryDetail", s.handleGetDictionaryDetail)
|
||||
r.GET("/sysDictionaryDetail/getSysDictionaryDetailList", s.handleGetDictionaryDetailList)
|
||||
r.GET("/sysDictionaryDetail/getDictionaryTreeList", s.handleGetDictionaryTreeList)
|
||||
r.POST("/sysDictionaryDetail/getDictionaryDetailsByParent", s.handleGetDictionaryDetailsByParent)
|
||||
r.GET("/sysDictionaryDetail/getDictionaryListByType", s.handleGetDictionaryListByType)
|
||||
r.GET("/sysDictionaryDetail/getDictionaryTreeListByType", s.handleGetDictionaryTreeListByType)
|
||||
// 字典详情接口 - 写操作(带操作记录)
|
||||
rg.PrivateWithRecord.POST("/sysDictionaryDetail/createSysDictionaryDetail", s.handleCreateDictionaryDetail)
|
||||
rg.PrivateWithRecord.DELETE("/sysDictionaryDetail/deleteSysDictionaryDetail", s.handleDeleteDictionaryDetail)
|
||||
rg.PrivateWithRecord.PUT("/sysDictionaryDetail/updateSysDictionaryDetail", s.handleUpdateDictionaryDetail)
|
||||
|
||||
// 字典详情接口 - 读操作(不记录)
|
||||
rg.Private.GET("/sysDictionaryDetail/findSysDictionaryDetail", s.handleGetDictionaryDetail)
|
||||
rg.Private.GET("/sysDictionaryDetail/getSysDictionaryDetailList", s.handleGetDictionaryDetailList)
|
||||
rg.Private.GET("/sysDictionaryDetail/getDictionaryTreeList", s.handleGetDictionaryTreeList)
|
||||
rg.Private.GET("/sysDictionaryDetail/getDictionaryTreeListByType", s.handleGetDictionaryTreeListByType)
|
||||
rg.Private.GET("/sysDictionaryDetail/getDictionaryDetailsByParent", s.handleGetDictionaryDetailsByParent)
|
||||
rg.Private.GET("/sysDictionaryDetail/getDictionaryPath", s.handleGetDictionaryPath)
|
||||
}
|
||||
|
||||
// HTTP Handlers - Dictionary
|
||||
|
|
@ -568,6 +570,16 @@ func (s *DictionaryService) handleGetDictionaryTreeListByType(ctx http.Context)
|
|||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"list": resp}})
|
||||
}
|
||||
|
||||
func (s *DictionaryService) handleGetDictionaryPath(ctx http.Context) error {
|
||||
idStr := ctx.Query().Get("id")
|
||||
id, _ := parseUint(idStr)
|
||||
resp, err := s.uc.GetDictionaryPath(ctx, id)
|
||||
if err != nil {
|
||||
return errors.InternalServer("PATH_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"path": toDictionaryDetailInfoList(resp)}})
|
||||
}
|
||||
|
||||
// parseUint 解析uint
|
||||
func parseUint(s string) (uint, error) {
|
||||
if s == "" {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func (s *JwtBlacklistService) JoinBlacklist(ctx http.Context) error {
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *JwtBlacklistService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/jwt/jsonInBlacklist", s.JoinBlacklist)
|
||||
func (s *JwtBlacklistService) RegisterRoutes(rg *RouterGroup) {
|
||||
// JWT黑名单(不需要操作记录)
|
||||
rg.Private.POST("/jwt/jsonInBlacklist", s.JoinBlacklist)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,18 +322,19 @@ func toBaseMenuInfo(m *system.BaseMenu) *MenuInfo {
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *MenuService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
func (s *MenuService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 私有路由 + 操作记录(写操作)
|
||||
rg.PrivateWithRecord.POST("/menu/addBaseMenu", s.handleAddBaseMenu)
|
||||
rg.PrivateWithRecord.POST("/menu/addMenuAuthority", s.handleAddMenuAuthority)
|
||||
rg.PrivateWithRecord.POST("/menu/deleteBaseMenu", s.handleDeleteBaseMenu)
|
||||
rg.PrivateWithRecord.POST("/menu/updateBaseMenu", s.handleUpdateBaseMenu)
|
||||
|
||||
r.POST("/menu/getMenu", s.handleGetMenu)
|
||||
r.POST("/menu/getBaseMenuTree", s.handleGetBaseMenuTree)
|
||||
r.POST("/menu/getMenuList", s.handleGetMenuList)
|
||||
r.POST("/menu/addBaseMenu", s.handleAddBaseMenu)
|
||||
r.POST("/menu/updateBaseMenu", s.handleUpdateBaseMenu)
|
||||
r.POST("/menu/deleteBaseMenu", s.handleDeleteBaseMenu)
|
||||
r.POST("/menu/getBaseMenuById", s.handleGetBaseMenuById)
|
||||
r.POST("/menu/addMenuAuthority", s.handleAddMenuAuthority)
|
||||
r.POST("/menu/getMenuAuthority", s.handleGetMenuAuthority)
|
||||
// 私有路由(读操作,不记录)
|
||||
rg.Private.POST("/menu/getMenu", s.handleGetMenu)
|
||||
rg.Private.POST("/menu/getMenuList", s.handleGetMenuList)
|
||||
rg.Private.POST("/menu/getBaseMenuTree", s.handleGetBaseMenuTree)
|
||||
rg.Private.POST("/menu/getMenuAuthority", s.handleGetMenuAuthority)
|
||||
rg.Private.POST("/menu/getBaseMenuById", s.handleGetBaseMenuById)
|
||||
}
|
||||
|
||||
// HTTP Handlers
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
types "kra/internal/service/types/system"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
|
|
@ -21,18 +22,18 @@ func NewOperationRecordService(uc *system.OperationRecordUsecase) *OperationReco
|
|||
|
||||
// OperationRecordInfo 操作记录信息
|
||||
type OperationRecordInfo struct {
|
||||
ID uint `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Status int `json:"status"`
|
||||
Latency int64 `json:"latency"`
|
||||
Agent string `json:"agent"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
Body string `json:"body"`
|
||||
Resp string `json:"resp"`
|
||||
UserID uint `json:"user_id"`
|
||||
User *UserInfo `json:"user,omitempty"`
|
||||
ID uint `json:"id"`
|
||||
IP string `json:"ip"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Status int `json:"status"`
|
||||
Latency int64 `json:"latency"`
|
||||
Agent string `json:"agent"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
Body string `json:"body"`
|
||||
Resp string `json:"resp"`
|
||||
UserID uint `json:"user_id"`
|
||||
User *types.UserInfo `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// GetOperationRecordListRequest 获取操作记录列表请求
|
||||
|
|
@ -64,12 +65,12 @@ type DeleteOperationRecordByIdsRequest struct {
|
|||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *OperationRecordService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/sysOperationRecord/getSysOperationRecordList", s.handleGetList)
|
||||
r.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDelete)
|
||||
r.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteByIds)
|
||||
r.GET("/sysOperationRecord/findSysOperationRecord", s.handleFind)
|
||||
func (s *OperationRecordService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 操作记录(不需要操作记录中间件,避免递归)
|
||||
rg.Private.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDelete)
|
||||
rg.Private.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteByIds)
|
||||
rg.Private.GET("/sysOperationRecord/findSysOperationRecord", s.handleFind)
|
||||
rg.Private.GET("/sysOperationRecord/getSysOperationRecordList", s.handleGetList)
|
||||
}
|
||||
|
||||
func (s *OperationRecordService) handleGetList(ctx http.Context) error {
|
||||
|
|
@ -159,7 +160,8 @@ func toOperationRecordInfo(r *system.OperationRecord) *OperationRecordInfo {
|
|||
UserID: r.UserID,
|
||||
}
|
||||
if r.User != nil {
|
||||
info.User = toUserInfo(r.User)
|
||||
userInfo := toTypesUserInfo(r.User)
|
||||
info.User = &userInfo
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// ParamsService 系统参数服务
|
||||
type ParamsService struct {
|
||||
uc *system.ParamsUsecase
|
||||
}
|
||||
|
||||
// NewParamsService 创建系统参数服务
|
||||
func NewParamsService(uc *system.ParamsUsecase) *ParamsService {
|
||||
return &ParamsService{uc: uc}
|
||||
}
|
||||
|
||||
// ParamsInfo 参数信息
|
||||
type ParamsInfo struct {
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Desc string `json:"desc"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
// CreateParamsRequest 创建参数请求
|
||||
type CreateParamsRequest struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// UpdateParamsRequest 更新参数请求
|
||||
type UpdateParamsRequest struct {
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// GetParamsListRequest 获取参数列表请求
|
||||
type GetParamsListRequest struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Key string `json:"key" form:"key"`
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *ParamsService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 写操作(带操作记录)
|
||||
rg.PrivateWithRecord.POST("/sysParams/createSysParams", s.handleCreateParams)
|
||||
rg.PrivateWithRecord.DELETE("/sysParams/deleteSysParams", s.handleDeleteParams)
|
||||
rg.PrivateWithRecord.DELETE("/sysParams/deleteSysParamsByIds", s.handleDeleteParamsByIds)
|
||||
rg.PrivateWithRecord.PUT("/sysParams/updateSysParams", s.handleUpdateParams)
|
||||
|
||||
// 读操作(不记录)
|
||||
rg.Private.GET("/sysParams/findSysParams", s.handleFindParams)
|
||||
rg.Private.GET("/sysParams/getSysParamsList", s.handleGetParamsList)
|
||||
rg.Private.GET("/sysParams/getSysParam", s.handleGetParamByKey)
|
||||
}
|
||||
|
||||
// handleCreateParams 创建参数
|
||||
func (s *ParamsService) handleCreateParams(ctx http.Context) error {
|
||||
var req CreateParamsRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
params := &system.Params{
|
||||
Name: req.Name,
|
||||
Key: req.Key,
|
||||
Value: req.Value,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
if err := s.uc.Create(ctx, params); err != nil {
|
||||
return errors.BadRequest("CREATE_FAILED", "创建失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "创建成功"})
|
||||
}
|
||||
|
||||
// handleDeleteParams 删除参数
|
||||
func (s *ParamsService) handleDeleteParams(ctx http.Context) error {
|
||||
idStr := ctx.Query().Get("ID")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return errors.BadRequest("INVALID_ID", "无效的ID")
|
||||
}
|
||||
if err := s.uc.Delete(ctx, uint(id)); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", "删除失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"})
|
||||
}
|
||||
|
||||
// handleDeleteParamsByIds 批量删除参数
|
||||
func (s *ParamsService) handleDeleteParamsByIds(ctx http.Context) error {
|
||||
idsStr := ctx.Query()["IDs[]"]
|
||||
if len(idsStr) == 0 {
|
||||
return errors.BadRequest("INVALID_IDS", "无效的IDs")
|
||||
}
|
||||
ids := make([]uint, 0, len(idsStr))
|
||||
for _, idStr := range idsStr {
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, uint(id))
|
||||
}
|
||||
if err := s.uc.DeleteByIds(ctx, ids); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", "批量删除失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "批量删除成功"})
|
||||
}
|
||||
|
||||
// handleUpdateParams 更新参数
|
||||
func (s *ParamsService) handleUpdateParams(ctx http.Context) error {
|
||||
var req UpdateParamsRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
params := &system.Params{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Key: req.Key,
|
||||
Value: req.Value,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
if err := s.uc.Update(ctx, params); err != nil {
|
||||
return errors.BadRequest("UPDATE_FAILED", "更新失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "更新成功"})
|
||||
}
|
||||
|
||||
// handleFindParams 根据ID查询参数
|
||||
func (s *ParamsService) handleFindParams(ctx http.Context) error {
|
||||
idStr := ctx.Query().Get("ID")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return errors.BadRequest("INVALID_ID", "无效的ID")
|
||||
}
|
||||
params, err := s.uc.GetByID(ctx, uint(id))
|
||||
if err != nil {
|
||||
return errors.NotFound("NOT_FOUND", "查询失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "查询成功", "data": toParamsInfo(params)})
|
||||
}
|
||||
|
||||
// handleGetParamsList 分页获取参数列表
|
||||
func (s *ParamsService) handleGetParamsList(ctx http.Context) error {
|
||||
var req GetParamsListRequest
|
||||
if err := ctx.BindQuery(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
filters := make(map[string]interface{})
|
||||
if req.Name != "" {
|
||||
filters["name"] = req.Name
|
||||
}
|
||||
if req.Key != "" {
|
||||
filters["key"] = req.Key
|
||||
}
|
||||
list, total, err := s.uc.GetList(ctx, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
return errors.InternalServer("LIST_ERROR", "获取失败:"+err.Error())
|
||||
}
|
||||
result := make([]ParamsInfo, len(list))
|
||||
for i, p := range list {
|
||||
result[i] = toParamsInfo(p)
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "获取成功",
|
||||
"data": map[string]any{
|
||||
"list": result,
|
||||
"total": total,
|
||||
"page": req.Page,
|
||||
"pageSize": req.PageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetParamByKey 根据key获取参数
|
||||
func (s *ParamsService) handleGetParamByKey(ctx http.Context) error {
|
||||
key := ctx.Query().Get("key")
|
||||
if key == "" {
|
||||
return errors.BadRequest("INVALID_KEY", "无效的key")
|
||||
}
|
||||
params, err := s.uc.GetByKey(ctx, key)
|
||||
if err != nil {
|
||||
return errors.NotFound("NOT_FOUND", "获取失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": toParamsInfo(params)})
|
||||
}
|
||||
|
||||
// toParamsInfo 转换为ParamsInfo
|
||||
func toParamsInfo(p *system.Params) ParamsInfo {
|
||||
info := ParamsInfo{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Key: p.Key,
|
||||
Value: p.Value,
|
||||
Desc: p.Desc,
|
||||
}
|
||||
if !p.CreatedAt.IsZero() {
|
||||
info.CreatedAt = p.CreatedAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if !p.UpdatedAt.IsZero() {
|
||||
info.UpdatedAt = p.UpdatedAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"kra/pkg/server"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// SystemConfigService 系统配置服务
|
||||
type SystemConfigService struct {
|
||||
diskMountPoints []string
|
||||
}
|
||||
|
||||
// NewSystemConfigService 创建系统配置服务
|
||||
func NewSystemConfigService() *SystemConfigService {
|
||||
// 默认磁盘挂载点,可以从配置中读取
|
||||
return &SystemConfigService{
|
||||
diskMountPoints: []string{"/"},
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *SystemConfigService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 读操作(不记录)
|
||||
rg.Private.POST("/system/getServerInfo", s.handleGetServerInfo)
|
||||
}
|
||||
|
||||
// handleGetServerInfo 获取服务器信息
|
||||
func (s *SystemConfigService) handleGetServerInfo(ctx http.Context) error {
|
||||
serverInfo, err := server.GetServerInfo(s.diskMountPoints)
|
||||
if err != nil {
|
||||
return errors.InternalServer("GET_SERVER_INFO_FAILED", "获取失败:"+err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "获取成功",
|
||||
"data": map[string]any{"server": serverInfo},
|
||||
})
|
||||
}
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/server/middleware"
|
||||
types "kra/internal/service/types/system"
|
||||
"kra/pkg/jwt"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
khttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// RouterGroup 路由组接口
|
||||
type RouterGroup struct {
|
||||
Public *khttp.Router // 公开路由(无需认证)
|
||||
Private *khttp.Router // 私有路由(需要JWT认证)
|
||||
PrivateWithRecord *khttp.Router // 私有路由 + 操作记录
|
||||
}
|
||||
|
||||
// UserService 用户服务
|
||||
type UserService struct {
|
||||
uc *system.UserUsecase
|
||||
|
|
@ -24,93 +30,43 @@ func NewUserService(uc *system.UserUsecase, jwtPkg *jwt.JWT) *UserService {
|
|||
return &UserService{uc: uc, jwtPkg: jwtPkg}
|
||||
}
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
User *UserInfo `json:"user"`
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
// UserInfo 用户信息
|
||||
type UserInfo struct {
|
||||
ID uint `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
SideMode string `json:"sideMode"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
BaseColor string `json:"baseColor"`
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
OriginSetting json.RawMessage `json:"originSetting,omitempty"`
|
||||
Authority *AuthorityInfo `json:"authority,omitempty"`
|
||||
Authorities []*AuthorityInfo `json:"authorities,omitempty"`
|
||||
}
|
||||
|
||||
// AuthorityInfo 角色信息
|
||||
type AuthorityInfo struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityName string `json:"authorityName"`
|
||||
ParentId *uint `json:"parentId,omitempty"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *UserService) Login(ctx context.Context, req *LoginRequest) (*LoginResponse, error) {
|
||||
func (s *UserService) Login(ctx khttp.Context) error {
|
||||
var req types.LoginRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
user, err := s.uc.Login(ctx, req.Username, req.Password)
|
||||
if err != nil {
|
||||
return nil, errors.Unauthorized("LOGIN_FAILED", err.Error())
|
||||
return errors.Unauthorized("LOGIN_FAILED", err.Error())
|
||||
}
|
||||
|
||||
if user.Enable != 1 {
|
||||
return nil, errors.Forbidden("USER_DISABLED", "用户被禁止登录")
|
||||
return errors.Forbidden("USER_DISABLED", "用户被禁止登录")
|
||||
}
|
||||
|
||||
// 生成 JWT token
|
||||
claims := s.jwtPkg.CreateClaims(jwt.BaseClaims{
|
||||
UUID: user.UUID.String(),
|
||||
ID: uint(user.ID),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
NickName: user.NickName,
|
||||
AuthorityID: user.AuthorityId,
|
||||
})
|
||||
token, err := s.jwtPkg.CreateToken(claims.BaseClaims)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||
return errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||
}
|
||||
|
||||
return &LoginResponse{
|
||||
User: toUserInfo(user),
|
||||
return ctx.Result(200, &types.LoginReply{
|
||||
User: toTypesUserInfo(user),
|
||||
Token: token,
|
||||
ExpiresAt: claims.ExpiresAt.UnixMilli(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RegisterRequest 注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
})
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *UserService) Register(ctx context.Context, req *RegisterRequest) (*UserInfo, error) {
|
||||
func (s *UserService) Register(ctx khttp.Context) error {
|
||||
var req types.RegisterRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
user := &system.User{
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
|
|
@ -121,66 +77,60 @@ func (s *UserService) Register(ctx context.Context, req *RegisterRequest) (*User
|
|||
Email: req.Email,
|
||||
Enable: req.Enable,
|
||||
}
|
||||
|
||||
created, err := s.uc.Register(ctx, user)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("REGISTER_FAILED", err.Error())
|
||||
return errors.BadRequest("REGISTER_FAILED", err.Error())
|
||||
}
|
||||
|
||||
return toUserInfo(created), nil
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
Password string `json:"password"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
return ctx.Result(200, &types.UserInfoReply{User: toTypesUserInfo(created)})
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *UserService) ChangePassword(ctx context.Context, userID uint, req *ChangePasswordRequest) error {
|
||||
return s.uc.ChangePassword(ctx, userID, req.Password, req.NewPassword)
|
||||
}
|
||||
|
||||
// ResetPasswordRequest 重置密码请求
|
||||
type ResetPasswordRequest struct {
|
||||
ID uint `json:"id"`
|
||||
Password string `json:"password"`
|
||||
func (s *UserService) ChangePassword(ctx khttp.Context) error {
|
||||
var req types.ChangePasswordRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.uc.ChangePassword(ctx, userID, req.Password, req.NewPassword); err != nil {
|
||||
return errors.BadRequest("CHANGE_PASSWORD_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (s *UserService) ResetPassword(ctx context.Context, req *ResetPasswordRequest) error {
|
||||
return s.uc.ResetPassword(ctx, req.ID, req.Password)
|
||||
func (s *UserService) ResetPassword(ctx khttp.Context) error {
|
||||
var req types.ResetPasswordRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uc.ResetPassword(ctx, req.ID, req.Password); err != nil {
|
||||
return errors.BadRequest("RESET_PASSWORD_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (s *UserService) GetUserInfo(ctx context.Context, uuid string) (*UserInfo, error) {
|
||||
user, err := s.uc.GetUserInfo(ctx, uuid)
|
||||
if err != nil {
|
||||
return nil, errors.NotFound("USER_NOT_FOUND", err.Error())
|
||||
func (s *UserService) GetUserInfo(ctx khttp.Context) error {
|
||||
claims, ok := middleware.GetClaims(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
return toUserInfo(user), nil
|
||||
}
|
||||
|
||||
// GetUserListRequest 获取用户列表请求
|
||||
type GetUserListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// GetUserListResponse 获取用户列表响应
|
||||
type GetUserListResponse struct {
|
||||
List []*UserInfo `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
user, err := s.uc.GetUserInfo(ctx, claims.UUID)
|
||||
if err != nil {
|
||||
return errors.NotFound("USER_NOT_FOUND", err.Error())
|
||||
}
|
||||
return ctx.Result(200, &types.GetUserInfoReply{UserInfo: toTypesUserInfo(user)})
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
func (s *UserService) GetUserList(ctx context.Context, req *GetUserListRequest) (*GetUserListResponse, error) {
|
||||
func (s *UserService) GetUserList(ctx khttp.Context) error {
|
||||
var req types.GetUserListRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
filters := make(map[string]string)
|
||||
if req.Username != "" {
|
||||
filters["username"] = req.Username
|
||||
|
|
@ -194,45 +144,23 @@ func (s *UserService) GetUserList(ctx context.Context, req *GetUserListRequest)
|
|||
if req.Email != "" {
|
||||
filters["email"] = req.Email
|
||||
}
|
||||
|
||||
users, total, err := s.uc.GetUserList(ctx, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer("LIST_ERROR", err.Error())
|
||||
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||
}
|
||||
|
||||
list := make([]*UserInfo, len(users))
|
||||
list := make([]types.UserInfo, len(users))
|
||||
for i, u := range users {
|
||||
list[i] = toUserInfo(u)
|
||||
list[i] = toTypesUserInfo(u)
|
||||
}
|
||||
|
||||
return &GetUserListResponse{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetUserInfoRequest 设置用户信息请求(管理员用)
|
||||
type SetUserInfoRequest struct {
|
||||
ID uint `json:"id"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
return ctx.Result(200, &types.GetUserListReply{List: list, Total: total, Page: req.Page, PageSize: req.PageSize})
|
||||
}
|
||||
|
||||
// SetUserInfo 设置用户信息
|
||||
func (s *UserService) SetUserInfo(ctx context.Context, adminAuthorityID uint, req *SetUserInfoRequest) error {
|
||||
// 如果提供了AuthorityIds,先设置用户角色(与GVA保持一致)
|
||||
if len(req.AuthorityIds) > 0 {
|
||||
if err := s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds); err != nil {
|
||||
return err
|
||||
}
|
||||
func (s *UserService) SetUserInfo(ctx khttp.Context) error {
|
||||
var req types.SetUserInfoRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &system.User{
|
||||
ID: req.ID,
|
||||
NickName: req.NickName,
|
||||
|
|
@ -241,360 +169,177 @@ func (s *UserService) SetUserInfo(ctx context.Context, adminAuthorityID uint, re
|
|||
Email: req.Email,
|
||||
Enable: req.Enable,
|
||||
}
|
||||
return s.uc.SetUserInfo(ctx, user)
|
||||
if err := s.uc.SetUserInfo(ctx, user); err != nil {
|
||||
return errors.BadRequest("SET_USER_INFO_FAILED", err.Error())
|
||||
}
|
||||
if len(req.AuthorityIds) > 0 {
|
||||
adminAuthorityID := middleware.GetAuthorityID(ctx)
|
||||
if err := s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds); err != nil {
|
||||
return errors.BadRequest("SET_USER_AUTHORITIES_FAILED", err.Error())
|
||||
}
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// SetSelfInfoRequest 设置自己信息请求(用户用)
|
||||
type SetSelfInfoRequest struct {
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
SideMode string `json:"sideMode"`
|
||||
BaseColor string `json:"baseColor"`
|
||||
}
|
||||
|
||||
// SetSelfInfo 设置自己的信息
|
||||
func (s *UserService) SetSelfInfo(ctx context.Context, userID uint, req *SetSelfInfoRequest) error {
|
||||
// SetSelfInfo 设置自己信息
|
||||
func (s *UserService) SetSelfInfo(ctx khttp.Context) error {
|
||||
var req types.SetSelfInfoRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
user := &system.User{
|
||||
ID: userID,
|
||||
NickName: req.NickName,
|
||||
HeaderImg: req.HeaderImg,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
SideMode: req.SideMode,
|
||||
BaseColor: req.BaseColor,
|
||||
}
|
||||
return s.uc.SetSelfInfo(ctx, user)
|
||||
if err := s.uc.SetSelfInfo(ctx, user); err != nil {
|
||||
return errors.BadRequest("SET_SELF_INFO_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// SetSelfSetting 设置自己的配置
|
||||
func (s *UserService) SetSelfSetting(ctx context.Context, userID uint, setting json.RawMessage) error {
|
||||
return s.uc.SetSelfSetting(ctx, userID, setting)
|
||||
// SetSelfSetting 设置自己配置
|
||||
func (s *UserService) SetSelfSetting(ctx khttp.Context) error {
|
||||
var req types.SetSelfSettingRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
setting := json.RawMessage(req.Setting)
|
||||
if err := s.uc.SetSelfSetting(ctx, userID, setting); err != nil {
|
||||
return errors.BadRequest("SET_SELF_SETTING_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (s *UserService) DeleteUser(ctx context.Context, id uint) error {
|
||||
return s.uc.DeleteUser(ctx, id)
|
||||
}
|
||||
|
||||
// SetUserAuthorityRequest 设置用户角色请求
|
||||
type SetUserAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
}
|
||||
|
||||
// SetUserAuthorityResponse 设置用户角色响应
|
||||
type SetUserAuthorityResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
// SetUserAuthority 设置用户角色(切换角色)- 返回新token
|
||||
func (s *UserService) SetUserAuthority(ctx context.Context, userID uint, uuid string, username string, nickName string, req *SetUserAuthorityRequest) (*SetUserAuthorityResponse, error) {
|
||||
if err := s.uc.SetUserAuthority(ctx, userID, req.AuthorityId); err != nil {
|
||||
return nil, err
|
||||
func (s *UserService) DeleteUser(ctx khttp.Context) error {
|
||||
var req types.DeleteUserRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
currentUserID := middleware.GetUserID(ctx)
|
||||
if currentUserID == req.ID {
|
||||
return errors.BadRequest("DELETE_FAILED", "无法删除自己")
|
||||
}
|
||||
if err := s.uc.DeleteUser(ctx, req.ID); err != nil {
|
||||
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// 生成新的JWT token(与GVA保持一致)
|
||||
claims := s.jwtPkg.CreateClaims(jwt.BaseClaims{
|
||||
UUID: uuid,
|
||||
ID: userID,
|
||||
Username: username,
|
||||
NickName: nickName,
|
||||
AuthorityID: req.AuthorityId,
|
||||
})
|
||||
// SetUserAuthority 设置用户角色
|
||||
func (s *UserService) SetUserAuthority(ctx khttp.Context) error {
|
||||
var req types.SetUserAuthorityRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.uc.SetUserAuthority(ctx, userID, req.AuthorityId); err != nil {
|
||||
return errors.BadRequest("SET_AUTHORITY_FAILED", err.Error())
|
||||
}
|
||||
// 重新生成token
|
||||
claims, ok := middleware.GetClaims(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
claims.AuthorityID = req.AuthorityId
|
||||
token, err := s.jwtPkg.CreateToken(claims.BaseClaims)
|
||||
if err != nil {
|
||||
return nil, errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||
return errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||
}
|
||||
|
||||
return &SetUserAuthorityResponse{
|
||||
Token: token,
|
||||
ExpiresAt: claims.ExpiresAt.UnixMilli(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetUserAuthoritiesRequest 设置用户多角色请求
|
||||
type SetUserAuthoritiesRequest struct {
|
||||
ID uint `json:"id"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
return ctx.Result(200, &types.SetUserAuthorityReply{Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli()})
|
||||
}
|
||||
|
||||
// SetUserAuthorities 设置用户多角色
|
||||
func (s *UserService) SetUserAuthorities(ctx context.Context, adminAuthorityID uint, req *SetUserAuthoritiesRequest) error {
|
||||
return s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds)
|
||||
func (s *UserService) SetUserAuthorities(ctx khttp.Context) error {
|
||||
var req types.SetUserAuthoritiesRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
adminAuthorityID := middleware.GetAuthorityID(ctx)
|
||||
if err := s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds); err != nil {
|
||||
return errors.BadRequest("SET_AUTHORITIES_FAILED", err.Error())
|
||||
}
|
||||
return ctx.Result(200, nil)
|
||||
}
|
||||
|
||||
// 转换函数
|
||||
func toUserInfo(u *system.User) *UserInfo {
|
||||
info := &UserInfo{
|
||||
ID: u.ID,
|
||||
UUID: u.UUID.String(),
|
||||
Username: u.Username,
|
||||
NickName: u.NickName,
|
||||
SideMode: u.SideMode,
|
||||
HeaderImg: u.HeaderImg,
|
||||
BaseColor: u.BaseColor,
|
||||
AuthorityId: u.AuthorityId,
|
||||
Phone: u.Phone,
|
||||
Email: u.Email,
|
||||
Enable: u.Enable,
|
||||
OriginSetting: u.OriginSetting,
|
||||
// toTypesUserInfo 转换用户信息为types格式
|
||||
func toTypesUserInfo(user *system.User) types.UserInfo {
|
||||
if user == nil {
|
||||
return types.UserInfo{}
|
||||
}
|
||||
|
||||
if u.Authority != nil {
|
||||
info.Authority = &AuthorityInfo{
|
||||
AuthorityId: u.Authority.AuthorityId,
|
||||
AuthorityName: u.Authority.AuthorityName,
|
||||
ParentId: u.Authority.ParentId,
|
||||
DefaultRouter: u.Authority.DefaultRouter,
|
||||
info := types.UserInfo{
|
||||
ID: user.ID,
|
||||
UUID: user.UUID.String(),
|
||||
Username: user.Username,
|
||||
NickName: user.NickName,
|
||||
SideMode: user.SideMode,
|
||||
HeaderImg: user.HeaderImg,
|
||||
BaseColor: user.BaseColor,
|
||||
AuthorityId: user.AuthorityId,
|
||||
Phone: user.Phone,
|
||||
Email: user.Email,
|
||||
Enable: user.Enable,
|
||||
}
|
||||
if user.OriginSetting != nil {
|
||||
info.OriginSetting = string(user.OriginSetting)
|
||||
}
|
||||
if user.Authority != nil {
|
||||
auth := toTypesAuthorityInfo(user.Authority)
|
||||
info.Authority = &auth
|
||||
}
|
||||
if len(user.Authorities) > 0 {
|
||||
info.Authorities = make([]types.AuthorityInfo, len(user.Authorities))
|
||||
for i, a := range user.Authorities {
|
||||
info.Authorities[i] = toTypesAuthorityInfo(a)
|
||||
}
|
||||
}
|
||||
|
||||
if len(u.Authorities) > 0 {
|
||||
info.Authorities = make([]*AuthorityInfo, len(u.Authorities))
|
||||
for i, a := range u.Authorities {
|
||||
info.Authorities[i] = &AuthorityInfo{
|
||||
AuthorityId: a.AuthorityId,
|
||||
AuthorityName: a.AuthorityName,
|
||||
ParentId: a.ParentId,
|
||||
DefaultRouter: a.DefaultRouter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *UserService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
|
||||
// 公开接口
|
||||
r.POST("/base/login", s.handleLogin)
|
||||
|
||||
// 需要认证的接口
|
||||
r.POST("/user/register", s.handleRegister)
|
||||
r.POST("/user/changePassword", s.handleChangePassword)
|
||||
r.POST("/user/resetPassword", s.handleResetPassword)
|
||||
r.GET("/user/getUserInfo", s.handleGetUserInfo)
|
||||
r.POST("/user/getUserList", s.handleGetUserList)
|
||||
r.PUT("/user/setUserInfo", s.handleSetUserInfo)
|
||||
r.PUT("/user/setSelfInfo", s.handleSetSelfInfo)
|
||||
r.PUT("/user/setSelfSetting", s.handleSetSelfSetting)
|
||||
r.DELETE("/user/deleteUser", s.handleDeleteUser)
|
||||
r.POST("/user/setUserAuthority", s.handleSetUserAuthority)
|
||||
r.POST("/user/setUserAuthorities", s.handleSetUserAuthorities)
|
||||
// toTypesAuthorityInfo 转换角色信息为types格式
|
||||
func toTypesAuthorityInfo(auth *system.Authority) types.AuthorityInfo {
|
||||
if auth == nil {
|
||||
return types.AuthorityInfo{}
|
||||
}
|
||||
return types.AuthorityInfo{
|
||||
AuthorityId: auth.AuthorityId,
|
||||
AuthorityName: auth.AuthorityName,
|
||||
ParentId: auth.ParentId,
|
||||
DefaultRouter: auth.DefaultRouter,
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP Handlers
|
||||
func (s *UserService) handleLogin(ctx http.Context) error {
|
||||
var req LoginRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.Login(ctx, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "登录成功",
|
||||
"data": resp,
|
||||
})
|
||||
}
|
||||
// RegisterRoutes 注册用户路由
|
||||
func (s *UserService) RegisterRoutes(rg *RouterGroup) {
|
||||
// 公开路由(无需认证)
|
||||
rg.Public.POST("/base/login", s.Login)
|
||||
|
||||
func (s *UserService) handleRegister(ctx http.Context) error {
|
||||
var req RegisterRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.Register(ctx, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "注册成功",
|
||||
"data": resp,
|
||||
})
|
||||
}
|
||||
// 私有路由 + 操作记录(写操作)
|
||||
rg.PrivateWithRecord.POST("/user/admin_register", s.Register)
|
||||
rg.PrivateWithRecord.POST("/user/changePassword", s.ChangePassword)
|
||||
rg.PrivateWithRecord.POST("/user/setUserAuthority", s.SetUserAuthority)
|
||||
rg.PrivateWithRecord.DELETE("/user/deleteUser", s.DeleteUser)
|
||||
rg.PrivateWithRecord.PUT("/user/setUserInfo", s.SetUserInfo)
|
||||
rg.PrivateWithRecord.PUT("/user/setSelfInfo", s.SetSelfInfo)
|
||||
rg.PrivateWithRecord.POST("/user/setUserAuthorities", s.SetUserAuthorities)
|
||||
rg.PrivateWithRecord.POST("/user/resetPassword", s.ResetPassword)
|
||||
rg.PrivateWithRecord.PUT("/user/setSelfSetting", s.SetSelfSetting)
|
||||
|
||||
func (s *UserService) handleChangePassword(ctx http.Context) error {
|
||||
var req ChangePasswordRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.ChangePassword(ctx, userID, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "修改成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleResetPassword(ctx http.Context) error {
|
||||
var req ResetPasswordRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.ResetPassword(ctx, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "重置成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleGetUserInfo(ctx http.Context) error {
|
||||
claims, ok := middleware.GetClaims(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
resp, err := s.GetUserInfo(ctx, claims.UUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "获取成功",
|
||||
"data": map[string]any{"userInfo": resp},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleGetUserList(ctx http.Context) error {
|
||||
var req GetUserListRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.GetUserList(ctx, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "获取成功",
|
||||
"data": resp,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleSetUserInfo(ctx http.Context) error {
|
||||
var req SetUserInfoRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
adminAuthorityID := middleware.GetAuthorityID(ctx)
|
||||
if adminAuthorityID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.SetUserInfo(ctx, adminAuthorityID, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "设置成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleSetSelfInfo(ctx http.Context) error {
|
||||
var req SetSelfInfoRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.SetSelfInfo(ctx, userID, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "设置成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleSetSelfSetting(ctx http.Context) error {
|
||||
var req json.RawMessage
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.SetSelfSetting(ctx, userID, req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "设置成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleDeleteUser(ctx http.Context) error {
|
||||
var req struct {
|
||||
ID uint `json:"id"`
|
||||
}
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
// 不能删除自己
|
||||
userID := middleware.GetUserID(ctx)
|
||||
if userID == req.ID {
|
||||
return errors.BadRequest("DELETE_SELF", "删除失败, 无法删除自己")
|
||||
}
|
||||
if err := s.DeleteUser(ctx, req.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleSetUserAuthority(ctx http.Context) error {
|
||||
var req SetUserAuthorityRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
claims, ok := middleware.GetClaims(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
resp, err := s.SetUserAuthority(ctx, claims.BaseClaims.ID, claims.UUID, claims.Username, claims.NickName, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 设置响应头(与GVA保持一致)
|
||||
ctx.Response().Header().Set("new-token", resp.Token)
|
||||
ctx.Response().Header().Set("new-expires-at", fmt.Sprintf("%d", resp.ExpiresAt/1000))
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "修改成功",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserService) handleSetUserAuthorities(ctx http.Context) error {
|
||||
var req SetUserAuthoritiesRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
adminAuthorityID := middleware.GetAuthorityID(ctx)
|
||||
if adminAuthorityID == 0 {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.SetUserAuthorities(ctx, adminAuthorityID, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "修改成功",
|
||||
})
|
||||
// 私有路由(读操作,不记录)
|
||||
rg.Private.POST("/user/getUserList", s.GetUserList)
|
||||
rg.Private.GET("/user/getUserInfo", s.GetUserInfo)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package common
|
||||
|
||||
// PageResult 分页响应
|
||||
type PageResult struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// IdsRequest 批量ID请求
|
||||
type IdsRequest struct {
|
||||
Ids []uint `json:"ids"`
|
||||
}
|
||||
|
||||
// IdRequest 单个ID请求
|
||||
type IdRequest struct {
|
||||
ID uint `json:"id"`
|
||||
}
|
||||
|
||||
// Empty 空响应
|
||||
type Empty struct{}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package system
|
||||
|
||||
// ApiInfo API信息
|
||||
type ApiInfo struct {
|
||||
ID uint `json:"ID"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// CreateApiRequest 创建API请求
|
||||
type CreateApiRequest struct {
|
||||
Path string `json:"path" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateApiRequest 更新API请求
|
||||
type UpdateApiRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// DeleteApiRequest 删除API请求
|
||||
type DeleteApiRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteApisByIdsRequest 批量删除API请求
|
||||
type DeleteApisByIdsRequest struct {
|
||||
Ids []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
// GetApiByIdRequest 根据ID获取API请求
|
||||
type GetApiByIdRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// GetApiByIdReply 根据ID获取API响应
|
||||
type GetApiByIdReply struct {
|
||||
Api ApiInfo `json:"api"`
|
||||
}
|
||||
|
||||
// GetApiListRequest 获取API列表请求
|
||||
type GetApiListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
Method string `json:"method"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
OrderKey string `json:"orderKey"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
||||
// GetApiListReply 获取API列表响应
|
||||
type GetApiListReply struct {
|
||||
List []ApiInfo `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// GetAllApisReply 获取所有API响应
|
||||
type GetAllApisReply struct {
|
||||
Apis []ApiInfo `json:"apis"`
|
||||
}
|
||||
|
||||
// GetApiGroupsReply 获取API分组响应
|
||||
type GetApiGroupsReply struct {
|
||||
Groups []string `json:"groups"`
|
||||
ApiGroupMap map[string]string `json:"apiGroupMap"`
|
||||
}
|
||||
|
||||
// SyncApiReply 同步API响应
|
||||
type SyncApiReply struct {
|
||||
NewApis []ApiInfo `json:"newApis"`
|
||||
DeleteApis []ApiInfo `json:"deleteApis"`
|
||||
IgnoreApis []ApiInfo `json:"ignoreApis"`
|
||||
}
|
||||
|
||||
// IgnoreApiRequest 忽略API请求
|
||||
type IgnoreApiRequest struct {
|
||||
Path string `json:"path" binding:"required"`
|
||||
Method string `json:"method" binding:"required"`
|
||||
Flag bool `json:"flag"`
|
||||
}
|
||||
|
||||
// EnterSyncApiRequest 确认同步API请求
|
||||
type EnterSyncApiRequest struct {
|
||||
NewApis []ApiInfo `json:"newApis"`
|
||||
DeleteApis []ApiInfo `json:"deleteApis"`
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package system
|
||||
|
||||
// UserInfo 用户信息
|
||||
type UserInfo struct {
|
||||
ID uint `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Username string `json:"userName"`
|
||||
NickName string `json:"nickName"`
|
||||
SideMode string `json:"sideMode"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
BaseColor string `json:"baseColor"`
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
OriginSetting string `json:"originSetting,omitempty"`
|
||||
Authority *AuthorityInfo `json:"authority,omitempty"`
|
||||
Authorities []AuthorityInfo `json:"authorities,omitempty"`
|
||||
}
|
||||
|
||||
// AuthorityInfo 角色信息
|
||||
type AuthorityInfo struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityName string `json:"authorityName"`
|
||||
ParentId *uint `json:"parentId,omitempty"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
}
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
}
|
||||
|
||||
// LoginReply 登录响应
|
||||
type LoginReply struct {
|
||||
User UserInfo `json:"user"`
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
// RegisterRequest 注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"userName" binding:"required"`
|
||||
Password string `json:"passWord" binding:"required"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
}
|
||||
|
||||
// UserInfoReply 用户信息响应
|
||||
type UserInfoReply struct {
|
||||
User UserInfo `json:"user"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
Password string `json:"password" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// ResetPasswordRequest 重置密码请求
|
||||
type ResetPasswordRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// GetUserInfoReply 获取用户信息响应
|
||||
type GetUserInfoReply struct {
|
||||
UserInfo UserInfo `json:"userInfo"`
|
||||
}
|
||||
|
||||
// GetUserListRequest 获取用户列表请求
|
||||
type GetUserListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// GetUserListReply 获取用户列表响应
|
||||
type GetUserListReply struct {
|
||||
List []UserInfo `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// SetUserInfoRequest 设置用户信息请求
|
||||
type SetUserInfoRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
}
|
||||
|
||||
// SetSelfInfoRequest 设置自己信息请求
|
||||
type SetSelfInfoRequest struct {
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
SideMode string `json:"sideMode"`
|
||||
BaseColor string `json:"baseColor"`
|
||||
}
|
||||
|
||||
// SetSelfSettingRequest 设置自己配置请求
|
||||
type SetSelfSettingRequest struct {
|
||||
Setting string `json:"setting"`
|
||||
}
|
||||
|
||||
// DeleteUserRequest 删除用户请求
|
||||
type DeleteUserRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// SetUserAuthorityRequest 设置用户角色请求
|
||||
type SetUserAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
}
|
||||
|
||||
// SetUserAuthorityReply 设置用户角色响应
|
||||
type SetUserAuthorityReply struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
// SetUserAuthoritiesRequest 设置用户多角色请求
|
||||
type SetUserAuthoritiesRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
AuthorityIds []uint `json:"authorityIds" binding:"required"`
|
||||
}
|
||||
|
|
@ -120,3 +120,16 @@ func (j *JWT) CreateTokenByOldToken(oldToken string, oldClaims CustomClaims) (st
|
|||
}
|
||||
return newToken, &oldClaims, nil
|
||||
}
|
||||
|
||||
// NewJWTFromConfig 从配置创建JWT实例(内部使用)
|
||||
func NewJWTFromConfig(signingKey, issuer, expiresTime, bufferTime string) *JWT {
|
||||
expires, _ := time.ParseDuration(expiresTime)
|
||||
if expires == 0 {
|
||||
expires = 7 * 24 * time.Hour // 默认7天
|
||||
}
|
||||
buffer, _ := time.ParseDuration(bufferTime)
|
||||
if buffer == 0 {
|
||||
buffer = 24 * time.Hour // 默认1天
|
||||
}
|
||||
return NewJWT(signingKey, issuer, expires, buffer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// NewJWTFromConf 从conf.JWT配置创建JWT实例(用于wire注入)
|
||||
func NewJWTFromConf(c *conf.JWT) *JWT {
|
||||
return NewJWTFromConfig(c.SigningKey, c.Issuer, c.ExpiresTime, c.BufferTime)
|
||||
}
|
||||
|
||||
// ProviderSet is jwt providers for wire.
|
||||
var ProviderSet = wire.NewSet(NewJWTFromConf)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
)
|
||||
|
||||
// Response 统一响应格式
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Encoder 响应编码器,包装响应为 {code, msg, data} 格式
|
||||
func Encoder(w http.ResponseWriter, r *http.Request, v interface{}) error {
|
||||
if v == nil {
|
||||
v = struct{}{}
|
||||
}
|
||||
reply := Response{
|
||||
Code: 0,
|
||||
Msg: "success",
|
||||
Data: v,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
return json.NewEncoder(w).Encode(reply)
|
||||
}
|
||||
|
||||
// ErrorEncoder 错误编码器,统一错误响应格式
|
||||
func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
|
||||
se := errors.FromError(err)
|
||||
code := int(se.Code)
|
||||
if code >= 400 && code < 500 {
|
||||
code = 7
|
||||
} else if code >= 500 {
|
||||
code = 7
|
||||
}
|
||||
reply := Response{
|
||||
Code: code,
|
||||
Msg: se.Message,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(int(se.Code))
|
||||
json.NewEncoder(w).Encode(reply)
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
const (
|
||||
B = 1
|
||||
KB = 1024 * B
|
||||
MB = 1024 * KB
|
||||
GB = 1024 * MB
|
||||
)
|
||||
|
||||
// Server 服务器信息
|
||||
type Server struct {
|
||||
Os Os `json:"os"`
|
||||
Cpu Cpu `json:"cpu"`
|
||||
Ram Ram `json:"ram"`
|
||||
Disk []Disk `json:"disk"`
|
||||
}
|
||||
|
||||
// Os 操作系统信息
|
||||
type Os struct {
|
||||
GOOS string `json:"goos"`
|
||||
NumCPU int `json:"numCpu"`
|
||||
Compiler string `json:"compiler"`
|
||||
GoVersion string `json:"goVersion"`
|
||||
NumGoroutine int `json:"numGoroutine"`
|
||||
}
|
||||
|
||||
// Cpu CPU信息
|
||||
type Cpu struct {
|
||||
Cpus []float64 `json:"cpus"`
|
||||
Cores int `json:"cores"`
|
||||
}
|
||||
|
||||
// Ram 内存信息
|
||||
type Ram struct {
|
||||
UsedMB int `json:"usedMb"`
|
||||
TotalMB int `json:"totalMb"`
|
||||
UsedPercent int `json:"usedPercent"`
|
||||
}
|
||||
|
||||
// Disk 磁盘信息
|
||||
type Disk struct {
|
||||
MountPoint string `json:"mountPoint"`
|
||||
UsedMB int `json:"usedMb"`
|
||||
UsedGB int `json:"usedGb"`
|
||||
TotalMB int `json:"totalMb"`
|
||||
TotalGB int `json:"totalGb"`
|
||||
UsedPercent int `json:"usedPercent"`
|
||||
}
|
||||
|
||||
// InitOS 获取操作系统信息
|
||||
func InitOS() Os {
|
||||
return Os{
|
||||
GOOS: runtime.GOOS,
|
||||
NumCPU: runtime.NumCPU(),
|
||||
Compiler: runtime.Compiler,
|
||||
GoVersion: runtime.Version(),
|
||||
NumGoroutine: runtime.NumGoroutine(),
|
||||
}
|
||||
}
|
||||
|
||||
// InitCPU 获取CPU信息
|
||||
func InitCPU() (Cpu, error) {
|
||||
var c Cpu
|
||||
cores, err := cpu.Counts(false)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.Cores = cores
|
||||
|
||||
cpus, err := cpu.Percent(time.Duration(200)*time.Millisecond, true)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.Cpus = cpus
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// InitRAM 获取内存信息
|
||||
func InitRAM() (Ram, error) {
|
||||
var r Ram
|
||||
u, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r.UsedMB = int(u.Used) / MB
|
||||
r.TotalMB = int(u.Total) / MB
|
||||
r.UsedPercent = int(u.UsedPercent)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// InitDisk 获取磁盘信息
|
||||
func InitDisk(mountPoints []string) ([]Disk, error) {
|
||||
var d []Disk
|
||||
for _, mp := range mountPoints {
|
||||
u, err := disk.Usage(mp)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d = append(d, Disk{
|
||||
MountPoint: mp,
|
||||
UsedMB: int(u.Used) / MB,
|
||||
UsedGB: int(u.Used) / GB,
|
||||
TotalMB: int(u.Total) / MB,
|
||||
TotalGB: int(u.Total) / GB,
|
||||
UsedPercent: int(u.UsedPercent),
|
||||
})
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// GetServerInfo 获取服务器完整信息
|
||||
func GetServerInfo(diskMountPoints []string) (*Server, error) {
|
||||
var s Server
|
||||
var err error
|
||||
|
||||
s.Os = InitOS()
|
||||
|
||||
if s.Cpu, err = InitCPU(); err != nil {
|
||||
return &s, err
|
||||
}
|
||||
|
||||
if s.Ram, err = InitRAM(); err != nil {
|
||||
return &s, err
|
||||
}
|
||||
|
||||
if len(diskMountPoints) > 0 {
|
||||
if s.Disk, err = InitDisk(diskMountPoints); err != nil {
|
||||
return &s, err
|
||||
}
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
|
@ -168,7 +168,6 @@ export default defineConfig({
|
|||
mock: {
|
||||
include: ["mock/**/*", "src/pages/**/_mock.ts"],
|
||||
},
|
||||
utoopack: {},
|
||||
requestRecord: {},
|
||||
exportStatic: {},
|
||||
define: {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ export default {
|
|||
* @doc https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
dev: {
|
||||
"/v1/": {
|
||||
"/api/": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
pathRewrite: { "^/api": "" },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -62,6 +62,18 @@ export default [
|
|||
icon: 'fileSearch',
|
||||
component: './system/operation',
|
||||
},
|
||||
{
|
||||
path: '/system/params',
|
||||
name: 'params',
|
||||
icon: 'control',
|
||||
component: './system/params',
|
||||
},
|
||||
{
|
||||
path: '/system/state',
|
||||
name: 'state',
|
||||
icon: 'monitor',
|
||||
component: './system/state',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import {
|
|||
Question,
|
||||
SelectLang,
|
||||
} from "@/components";
|
||||
import { createAdminService } from "@/services/index";
|
||||
import { Admin } from "@/services/kratos/admin/v1/index";
|
||||
import { getUserInfo } from "@/services/system/user";
|
||||
import { LinkOutlined } from "@ant-design/icons";
|
||||
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
|
||||
import { SettingDrawer } from "@ant-design/pro-components";
|
||||
|
|
@ -19,20 +18,26 @@ const isDev = process.env.NODE_ENV === "development";
|
|||
const isDevOrTest = isDev || process.env.CI;
|
||||
const loginPath = "/user/login";
|
||||
|
||||
const adminService = createAdminService();
|
||||
|
||||
/**
|
||||
* @see https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||
* */
|
||||
export async function getInitialState(): Promise<{
|
||||
settings?: Partial<LayoutSettings>;
|
||||
currentUser?: API.CurrentUser;
|
||||
currentUser?: API.UserInfo;
|
||||
loading?: boolean;
|
||||
fetchUserInfo?: () => Promise<Admin | undefined>;
|
||||
fetchUserInfo?: () => Promise<API.UserInfo | undefined>;
|
||||
}> {
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
return await adminService.Current({});
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
const res = await getUserInfo();
|
||||
if (res.code === 0 && res.data?.userInfo) {
|
||||
return res.data.userInfo;
|
||||
}
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { createAdminService } from "@/services/index";
|
||||
import {
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
|
|
@ -12,8 +11,6 @@ import React from "react";
|
|||
import { flushSync } from "react-dom";
|
||||
import HeaderDropdown from "../HeaderDropdown";
|
||||
|
||||
const adminService = createAdminService();
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
menu?: boolean;
|
||||
children?: React.ReactNode;
|
||||
|
|
@ -22,7 +19,7 @@ export type GlobalHeaderRightProps = {
|
|||
export const AvatarName = () => {
|
||||
const { initialState } = useModel("@@initialState");
|
||||
const { currentUser } = initialState || {};
|
||||
return <span className="anticon">{currentUser?.name}</span>;
|
||||
return <span className="anticon">{currentUser?.nickName || currentUser?.userName}</span>;
|
||||
};
|
||||
|
||||
const useStyles = createStyles(({ token }) => {
|
||||
|
|
@ -51,7 +48,9 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
|||
* 退出登录,并且将当前的 url 保存
|
||||
*/
|
||||
const loginOut = async () => {
|
||||
await adminService.Logout({});
|
||||
// 清除本地存储的token和用户信息
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userInfo');
|
||||
const { search, pathname } = window.location;
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
const searchParams = new URLSearchParams({
|
||||
|
|
@ -80,6 +79,10 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
|||
loginOut();
|
||||
return;
|
||||
}
|
||||
if (key === "center") {
|
||||
history.push('/person');
|
||||
return;
|
||||
}
|
||||
history.push(`/account/${key}`);
|
||||
};
|
||||
|
||||
|
|
@ -101,7 +104,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
|||
|
||||
const { currentUser } = initialState;
|
||||
|
||||
if (!currentUser || !currentUser.name) {
|
||||
if (!currentUser || (!currentUser.nickName && !currentUser.userName)) {
|
||||
return loading;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
.dashboard {
|
||||
:global {
|
||||
.ant-page-header {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
|
||||
.statContent {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.statInfo {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.statValue {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.statTrend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statChart {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.chartCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.quickCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
min-height: 280px;
|
||||
|
||||
.shortcutItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #f0f5ff;
|
||||
|
||||
.shortcutIcon {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shortcutIcon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.shortcutTitle {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.noticeCard,
|
||||
.docCard,
|
||||
.sysCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
.sysCard {
|
||||
.sysItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.statCard {
|
||||
.statChart {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.quickCard {
|
||||
.shortcutItem {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.shortcutIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.shortcutTitle {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,268 @@
|
|||
/**
|
||||
* 仪表盘页面
|
||||
* 参考 GVA 的 Dashboard 布局
|
||||
*/
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Card, Col, Row, Statistic } from 'antd';
|
||||
import { UserOutlined, TeamOutlined, ApiOutlined, MenuOutlined } from '@ant-design/icons';
|
||||
import { Card, Col, Row, Statistic, Tag, List, Typography, Progress, Space, Tooltip } from 'antd';
|
||||
import {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
ApiOutlined,
|
||||
MenuOutlined,
|
||||
RiseOutlined,
|
||||
SettingOutlined,
|
||||
DatabaseOutlined,
|
||||
LinkOutlined,
|
||||
SafetyOutlined,
|
||||
CloudServerOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { history } from '@umijs/max';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
// 统计卡片数据
|
||||
const statsData = [
|
||||
{ title: '访问人数', value: 268500, icon: <UserOutlined />, trend: '+80%', color: '#1890ff', percent: 80 },
|
||||
{ title: '新增客户', value: 12580, icon: <TeamOutlined />, trend: '+25%', color: '#52c41a', percent: 65 },
|
||||
{ title: '解决数量', value: 8846, icon: <ApiOutlined />, trend: '+15%', color: '#722ed1', percent: 45 },
|
||||
];
|
||||
|
||||
// 快捷功能
|
||||
const shortcuts = [
|
||||
{ icon: <MenuOutlined />, title: '菜单管理', path: '/system/menu' },
|
||||
{ icon: <ApiOutlined />, title: 'API管理', path: '/system/api' },
|
||||
{ icon: <TeamOutlined />, title: '角色管理', path: '/system/role' },
|
||||
{ icon: <UserOutlined />, title: '用户管理', path: '/system/user' },
|
||||
{ icon: <DatabaseOutlined />, title: '字典管理', path: '/system/dict' },
|
||||
{ icon: <SettingOutlined />, title: '系统配置', path: '/system/config' },
|
||||
];
|
||||
|
||||
// 公告数据
|
||||
const notices = [
|
||||
{ type: 'processing', typeTitle: '公告', title: 'Kratos Admin v1.0 正式发布,欢迎使用!' },
|
||||
{ type: 'success', typeTitle: '通知', title: '系统已完成安全升级,请放心使用。' },
|
||||
{ type: 'warning', typeTitle: '警告', title: '请定期修改密码,确保账户安全。' },
|
||||
{ type: 'error', typeTitle: '重要', title: '数据库将于本周日凌晨进行维护。' },
|
||||
{ type: 'default', typeTitle: '信息', title: '感谢您对 Kratos Admin 的支持!' },
|
||||
];
|
||||
|
||||
// 内容数据
|
||||
const contentData = [
|
||||
{ name: '用户', value: 128, color: '#1890ff' },
|
||||
{ name: '角色', value: 8, color: '#52c41a' },
|
||||
{ name: 'API', value: 256, color: '#722ed1' },
|
||||
{ name: '菜单', value: 32, color: '#faad14' },
|
||||
{ name: '字典', value: 15, color: '#13c2c2' },
|
||||
{ name: '部门', value: 12, color: '#eb2f96' },
|
||||
];
|
||||
|
||||
// 文档链接
|
||||
const docLinks = [
|
||||
{ title: 'Kratos 官方文档', url: 'https://go-kratos.dev/' },
|
||||
{ title: 'Ant Design Pro', url: 'https://pro.ant.design/' },
|
||||
{ title: 'React 官方文档', url: 'https://react.dev/' },
|
||||
{ title: 'Go 语言文档', url: 'https://go.dev/doc/' },
|
||||
];
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="用户总数" value={128} prefix={<UserOutlined />} />
|
||||
<PageContainer
|
||||
header={{ title: '' }}
|
||||
className={styles.dashboard}
|
||||
>
|
||||
{/* 统计卡片行 */}
|
||||
<Row gutter={[16, 16]}>
|
||||
{statsData.map((item, index) => (
|
||||
<Col xs={24} sm={12} lg={8} key={index}>
|
||||
<Card className={styles.statCard} bordered={false}>
|
||||
<div className={styles.statContent}>
|
||||
<div className={styles.statInfo}>
|
||||
<Text type="secondary">{item.title}</Text>
|
||||
<div className={styles.statValue}>
|
||||
<Statistic value={item.value} valueStyle={{ fontSize: 28, fontWeight: 600 }} />
|
||||
</div>
|
||||
<div className={styles.statTrend}>
|
||||
<RiseOutlined style={{ color: '#52c41a' }} />
|
||||
<Text style={{ color: '#52c41a', marginLeft: 4 }}>{item.trend}</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.statChart}>
|
||||
<Progress
|
||||
type="circle"
|
||||
percent={item.percent}
|
||||
size={80}
|
||||
strokeColor={item.color}
|
||||
format={() => <span style={{ fontSize: 14 }}>{item.percent}%</span>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||
{/* 左侧内容数据 */}
|
||||
<Col xs={24} lg={18}>
|
||||
<Card title="内容数据" bordered={false} className={styles.chartCard}>
|
||||
<Row gutter={[16, 24]}>
|
||||
{contentData.map((item, index) => (
|
||||
<Col xs={12} sm={8} md={4} key={index}>
|
||||
<div className={styles.dataItem}>
|
||||
<div className={styles.dataValue} style={{ color: item.color }}>
|
||||
{item.value}
|
||||
</div>
|
||||
<div className={styles.dataName}>{item.name}</div>
|
||||
<Progress
|
||||
percent={Math.min((item.value / 300) * 100, 100)}
|
||||
showInfo={false}
|
||||
strokeColor={item.color}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<div className={styles.progressSection}>
|
||||
<div className={styles.progressTitle}>系统资源使用情况</div>
|
||||
<Row gutter={[32, 16]} style={{ marginTop: 16 }}>
|
||||
<Col xs={24} sm={12}>
|
||||
<div className={styles.progressItem}>
|
||||
<div className={styles.progressLabel}>
|
||||
<CloudServerOutlined /> CPU 使用率
|
||||
</div>
|
||||
<Progress percent={45} strokeColor="#1890ff" />
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<div className={styles.progressItem}>
|
||||
<div className={styles.progressLabel}>
|
||||
<DatabaseOutlined /> 内存使用率
|
||||
</div>
|
||||
<Progress percent={68} strokeColor="#52c41a" />
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<div className={styles.progressItem}>
|
||||
<div className={styles.progressLabel}>
|
||||
<SafetyOutlined /> 磁盘使用率
|
||||
</div>
|
||||
<Progress percent={32} strokeColor="#722ed1" />
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<div className={styles.progressItem}>
|
||||
<div className={styles.progressLabel}>
|
||||
<ApiOutlined /> API 调用量
|
||||
</div>
|
||||
<Progress percent={85} strokeColor="#faad14" />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="角色数量" value={8} prefix={<TeamOutlined />} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="API数量" value={256} prefix={<ApiOutlined />} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="菜单数量" value={32} prefix={<MenuOutlined />} />
|
||||
|
||||
{/* 右侧快捷功能 */}
|
||||
<Col xs={24} lg={6}>
|
||||
<Card title="快捷功能" bordered={false} className={styles.quickCard}>
|
||||
<Row gutter={[8, 16]}>
|
||||
{shortcuts.map((item, index) => (
|
||||
<Col span={8} key={index}>
|
||||
<div
|
||||
className={styles.shortcutItem}
|
||||
onClick={() => history.push(item.path)}
|
||||
>
|
||||
<div className={styles.shortcutIcon}>{item.icon}</div>
|
||||
<Text className={styles.shortcutTitle}>{item.title}</Text>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 底部区域 */}
|
||||
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
|
||||
{/* 公告 */}
|
||||
<Col xs={24} md={12} lg={8}>
|
||||
<Card
|
||||
title="公告"
|
||||
bordered={false}
|
||||
extra={<a>查看更多</a>}
|
||||
className={styles.noticeCard}
|
||||
>
|
||||
<List
|
||||
size="small"
|
||||
dataSource={notices}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{ padding: '8px 0', border: 'none' }}>
|
||||
<Space>
|
||||
<Tag color={item.type}>{item.typeTitle}</Tag>
|
||||
<Tooltip title={item.title}>
|
||||
<Text ellipsis style={{ maxWidth: 200 }}>{item.title}</Text>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 文档链接 */}
|
||||
<Col xs={24} md={12} lg={8}>
|
||||
<Card
|
||||
title="文档"
|
||||
bordered={false}
|
||||
extra={<a>查看更多</a>}
|
||||
className={styles.docCard}
|
||||
>
|
||||
<List
|
||||
size="small"
|
||||
dataSource={docLinks}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{ padding: '8px 0', border: 'none' }}>
|
||||
<a href={item.url} target="_blank" rel="noopener noreferrer">
|
||||
<Space>
|
||||
<LinkOutlined />
|
||||
<Text>{item.title}</Text>
|
||||
</Space>
|
||||
</a>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 系统信息 */}
|
||||
<Col xs={24} lg={8}>
|
||||
<Card title="系统信息" bordered={false} className={styles.sysCard}>
|
||||
<div className={styles.sysItem}>
|
||||
<Text type="secondary">系统名称</Text>
|
||||
<Text strong>Kratos Admin</Text>
|
||||
</div>
|
||||
<div className={styles.sysItem}>
|
||||
<Text type="secondary">后端框架</Text>
|
||||
<Text strong>Go Kratos</Text>
|
||||
</div>
|
||||
<div className={styles.sysItem}>
|
||||
<Text type="secondary">前端框架</Text>
|
||||
<Text strong>React + Ant Design Pro</Text>
|
||||
</div>
|
||||
<div className={styles.sysItem}>
|
||||
<Text type="secondary">版本号</Text>
|
||||
<Text strong>v1.0.0</Text>
|
||||
</div>
|
||||
<div className={styles.sysItem}>
|
||||
<Text type="secondary">服务器状态</Text>
|
||||
<Tag color="success">运行中</Tag>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Card style={{ marginTop: 16 }} title="欢迎使用 Kratos Admin">
|
||||
<p>这是一个基于 Kratos 框架的后台管理系统,前端使用 React + Ant Design Pro。</p>
|
||||
<p>功能与 GVA (gin-vue-admin) 保持一致。</p>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
* 系统参数管理页面
|
||||
* 与 GVA 保持一致的功能
|
||||
*/
|
||||
import { PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { PageContainer, ProTable, ModalForm, ProFormText, ProFormTextArea } from '@ant-design/pro-components';
|
||||
import type { ProColumns, ActionType } from '@ant-design/pro-components';
|
||||
import { Button, message, Popconfirm, Space } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import {
|
||||
getSysParamsList,
|
||||
createSysParams,
|
||||
updateSysParams,
|
||||
deleteSysParams,
|
||||
deleteSysParamsByIds,
|
||||
} from '@/services/system/params';
|
||||
|
||||
const ParamsPage: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.SysParams>();
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
const res = await deleteSysParams({ ID: id });
|
||||
if (res.code === 0) {
|
||||
message.success('删除成功');
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedRowKeys.length === 0) {
|
||||
message.warning('请选择要删除的记录');
|
||||
return;
|
||||
}
|
||||
const res = await deleteSysParamsByIds({ 'IDs[]': selectedRowKeys });
|
||||
if (res.code === 0) {
|
||||
message.success('批量删除成功');
|
||||
setSelectedRowKeys([]);
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ProColumns<API.SysParams>[] = [
|
||||
{ title: 'ID', dataIndex: 'ID', search: false, width: 80 },
|
||||
{ title: '参数名', dataIndex: 'name', width: 150 },
|
||||
{ title: '参数键', dataIndex: 'key', width: 150 },
|
||||
{ title: '参数值', dataIndex: 'value', ellipsis: true, search: false },
|
||||
{ title: '描述', dataIndex: 'desc', ellipsis: true, search: false },
|
||||
{ title: '创建时间', dataIndex: 'createdAt', search: false, width: 180 },
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 150,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setCurrentRow(record);
|
||||
setIsEdit(true);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(record.ID!)}>
|
||||
<Button type="link" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.SysParams>
|
||||
headerTitle="参数列表"
|
||||
actionRef={actionRef}
|
||||
rowKey="ID"
|
||||
columns={columns}
|
||||
rowSelection={{
|
||||
selectedRowKeys,
|
||||
onChange: (keys) => setSelectedRowKeys(keys as number[]),
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="add"
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setCurrentRow(undefined);
|
||||
setIsEdit(false);
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
新增参数
|
||||
</Button>,
|
||||
<Popconfirm key="batch" title="确定批量删除?" onConfirm={handleBatchDelete}>
|
||||
<Button danger icon={<DeleteOutlined />} disabled={selectedRowKeys.length === 0}>
|
||||
批量删除
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
]}
|
||||
request={async (params) => {
|
||||
const res = await getSysParamsList({
|
||||
page: params.current,
|
||||
pageSize: params.pageSize,
|
||||
name: params.name,
|
||||
key: params.key,
|
||||
});
|
||||
return {
|
||||
data: res.data?.list || [],
|
||||
total: res.data?.total || 0,
|
||||
success: res.code === 0,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
|
||||
<ModalForm
|
||||
title={isEdit ? '编辑参数' : '新增参数'}
|
||||
open={modalVisible}
|
||||
onOpenChange={setModalVisible}
|
||||
initialValues={currentRow}
|
||||
modalProps={{ destroyOnClose: true }}
|
||||
onFinish={async (values) => {
|
||||
let res;
|
||||
if (isEdit) {
|
||||
res = await updateSysParams({ ...currentRow, ...values });
|
||||
} else {
|
||||
res = await createSysParams(values);
|
||||
}
|
||||
if (res.code === 0) {
|
||||
message.success(isEdit ? '编辑成功' : '创建成功');
|
||||
setModalVisible(false);
|
||||
actionRef.current?.reload();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<ProFormText name="name" label="参数名" rules={[{ required: true, message: '请输入参数名' }]} />
|
||||
<ProFormText name="key" label="参数键" rules={[{ required: true, message: '请输入参数键' }]} />
|
||||
<ProFormTextArea name="value" label="参数值" rules={[{ required: true, message: '请输入参数值' }]} />
|
||||
<ProFormTextArea name="desc" label="描述" />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParamsPage;
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* 系统状态页面
|
||||
* 与 GVA 保持一致的功能
|
||||
*/
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Card, Row, Col, Progress, Statistic, Spin, Button } from 'antd';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getServerInfo } from '@/services/system/systemConfig';
|
||||
|
||||
const StatePage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [serverInfo, setServerInfo] = useState<API.ServerInfo>();
|
||||
|
||||
const fetchServerInfo = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getServerInfo();
|
||||
if (res.code === 0 && res.data?.server) {
|
||||
setServerInfo(res.data.server);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchServerInfo();
|
||||
}, []);
|
||||
|
||||
const getCpuAvg = () => {
|
||||
if (!serverInfo?.cpu?.cpus?.length) return 0;
|
||||
const sum = serverInfo.cpu.cpus.reduce((a, b) => a + b, 0);
|
||||
return Math.round(sum / serverInfo.cpu.cpus.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
extra={
|
||||
<Button icon={<ReloadOutlined />} onClick={fetchServerInfo} loading={loading}>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Card title="操作系统信息">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Statistic title="操作系统" value={serverInfo?.os?.goos || '-'} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="CPU核心数" value={serverInfo?.os?.numCpu || 0} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="Go版本" value={serverInfo?.os?.goVersion || '-'} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="Goroutine数" value={serverInfo?.os?.numGoroutine || 0} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card title="CPU使用率">
|
||||
<Progress
|
||||
type="dashboard"
|
||||
percent={getCpuAvg()}
|
||||
format={(percent) => `${percent}%`}
|
||||
status={getCpuAvg() > 80 ? 'exception' : 'normal'}
|
||||
/>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Statistic title="CPU核心数" value={serverInfo?.cpu?.cores || 0} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card title="内存使用">
|
||||
<Progress
|
||||
type="dashboard"
|
||||
percent={serverInfo?.ram?.usedPercent || 0}
|
||||
format={(percent) => `${percent}%`}
|
||||
status={(serverInfo?.ram?.usedPercent || 0) > 80 ? 'exception' : 'normal'}
|
||||
/>
|
||||
<Row gutter={16} style={{ marginTop: 16 }}>
|
||||
<Col span={12}>
|
||||
<Statistic title="已用内存" value={serverInfo?.ram?.usedMb || 0} suffix="MB" />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="总内存" value={serverInfo?.ram?.totalMb || 0} suffix="MB" />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card title="磁盘使用">
|
||||
{serverInfo?.disk?.map((d, i) => (
|
||||
<div key={i} style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 8 }}>{d.mountPoint}</div>
|
||||
<Progress
|
||||
percent={d.usedPercent}
|
||||
status={d.usedPercent > 80 ? 'exception' : 'normal'}
|
||||
/>
|
||||
<Row gutter={16} style={{ marginTop: 8 }}>
|
||||
<Col span={12}>
|
||||
<Statistic title="已用" value={d.usedGb} suffix="GB" valueStyle={{ fontSize: 14 }} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="总计" value={d.totalGb} suffix="GB" valueStyle={{ fontSize: 14 }} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatePage;
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #194bfb;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.leftSection {
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.oblique {
|
||||
position: absolute;
|
||||
right: -100px;
|
||||
top: -15%;
|
||||
width: 60%;
|
||||
height: 130%;
|
||||
background: #fff;
|
||||
transform: rotate(-12deg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.formWrapper {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 380px;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 44px;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: #194bfb;
|
||||
}
|
||||
}
|
||||
|
||||
.inputIcon {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.captchaRow {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.captchaInput {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.captchaImg {
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
background: #c3d4f2;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.loginBtn {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background: #194bfb;
|
||||
border-color: #194bfb;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(25, 75, 251, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: #1240d9;
|
||||
border-color: #1240d9;
|
||||
}
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
background: #194bfb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bannerContent {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
padding: 40px;
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.featureItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.featureIcon {
|
||||
font-size: 32px !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 12px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.leftSection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oblique {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formWrapper {
|
||||
width: 90%;
|
||||
max-width: 380px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,23 @@
|
|||
/**
|
||||
* 登录页面
|
||||
* 与 GVA 保持一致的功能
|
||||
* 参考 GVA 的 UI 风格
|
||||
*/
|
||||
import { LockOutlined, UserOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
|
||||
import { LoginForm, ProFormText } from '@ant-design/pro-components';
|
||||
import { history, useModel } from '@umijs/max';
|
||||
import { message, Image, Spin } from 'antd';
|
||||
import { message, Input, Button, Form, Spin } from 'antd';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { login, captcha } from '@/services/system/user';
|
||||
import styles from './index.less';
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const { setInitialState } = useModel('@@initialState');
|
||||
const [form] = Form.useForm();
|
||||
const [captchaInfo, setCaptchaInfo] = useState<{
|
||||
captchaId: string;
|
||||
picPath: string;
|
||||
openCaptcha: boolean;
|
||||
}>({ captchaId: '', picPath: '', openCaptcha: false });
|
||||
captchaLength: number;
|
||||
}>({ captchaId: '', picPath: '', openCaptcha: false, captchaLength: 6 });
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取验证码
|
||||
|
|
@ -27,6 +29,7 @@ const Login: React.FC = () => {
|
|||
captchaId: res.data.captchaId,
|
||||
picPath: res.data.picPath,
|
||||
openCaptcha: res.data.openCaptcha,
|
||||
captchaLength: res.data.captchaLength || 6,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -48,15 +51,12 @@ const Login: React.FC = () => {
|
|||
});
|
||||
if (res.code === 0 && res.data) {
|
||||
message.success('登录成功');
|
||||
// 存储token
|
||||
localStorage.setItem('token', res.data.token);
|
||||
localStorage.setItem('userInfo', JSON.stringify(res.data.user));
|
||||
// 更新全局状态
|
||||
await setInitialState((s) => ({
|
||||
...s,
|
||||
currentUser: res.data?.user,
|
||||
}));
|
||||
// 跳转到首页
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
history.push(urlParams.get('redirect') || '/');
|
||||
} else {
|
||||
|
|
@ -71,57 +71,122 @@ const Login: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||||
}}>
|
||||
<Spin spinning={loading}>
|
||||
<LoginForm
|
||||
title="Kratos Admin"
|
||||
subTitle="基于 Kratos 的后台管理系统"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{ username: 'admin' }}
|
||||
>
|
||||
<ProFormText
|
||||
name="username"
|
||||
fieldProps={{ size: 'large', prefix: <UserOutlined /> }}
|
||||
placeholder="请输入用户名"
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: 5, message: '用户名至少5个字符' },
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
fieldProps={{ size: 'large', prefix: <LockOutlined /> }}
|
||||
placeholder="请输入密码"
|
||||
rules={[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ min: 6, message: '密码至少6个字符' },
|
||||
]}
|
||||
/>
|
||||
{captchaInfo.openCaptcha && (
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 24 }}>
|
||||
<ProFormText
|
||||
name="captcha"
|
||||
fieldProps={{ size: 'large', prefix: <SafetyCertificateOutlined /> }}
|
||||
placeholder="请输入验证码"
|
||||
rules={[{ required: true, message: '请输入验证码' }]}
|
||||
/>
|
||||
<Image
|
||||
src={captchaInfo.picPath}
|
||||
alt="验证码"
|
||||
preview={false}
|
||||
style={{ height: 40, cursor: 'pointer' }}
|
||||
onClick={getCaptcha}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
{/* 左侧登录表单区域 */}
|
||||
<div className={styles.leftSection}>
|
||||
{/* 斜切装饰块 */}
|
||||
<div className={styles.oblique} />
|
||||
|
||||
<div className={styles.formWrapper}>
|
||||
<Spin spinning={loading}>
|
||||
{/* Logo 和标题 */}
|
||||
<div className={styles.header}>
|
||||
<div className={styles.logo}>
|
||||
<svg viewBox="0 0 24 24" width="48" height="48" fill="#194bfb">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"
|
||||
stroke="#194bfb" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className={styles.title}>Kratos Admin</h1>
|
||||
<p className={styles.subtitle}>基于 Go Kratos 和 React 的后台管理系统</p>
|
||||
</div>
|
||||
|
||||
{/* 登录表单 */}
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{ username: 'admin' }}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: 5, message: '用户名至少5个字符' },
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined className={styles.inputIcon} />}
|
||||
placeholder="请输入用户名"
|
||||
className={styles.input}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ min: 6, message: '密码至少6个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined className={styles.inputIcon} />}
|
||||
placeholder="请输入密码"
|
||||
className={styles.input}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{captchaInfo.openCaptcha && (
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
rules={[
|
||||
{ required: true, message: '请输入验证码' },
|
||||
{ pattern: /^\d+$/, message: '验证码须为数字' },
|
||||
]}
|
||||
>
|
||||
<div className={styles.captchaRow}>
|
||||
<Input
|
||||
prefix={<SafetyCertificateOutlined className={styles.inputIcon} />}
|
||||
placeholder="请输入验证码"
|
||||
className={styles.captchaInput}
|
||||
/>
|
||||
<div className={styles.captchaImg} onClick={getCaptcha}>
|
||||
{captchaInfo.picPath && (
|
||||
<img src={captchaInfo.picPath} alt="验证码" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block className={styles.loginBtn}>
|
||||
登 录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧图片区域 */}
|
||||
<div className={styles.rightSection}>
|
||||
<div className={styles.bannerContent}>
|
||||
<h2>欢迎使用 Kratos Admin</h2>
|
||||
<p>高效、安全、易用的企业级后台管理系统</p>
|
||||
<div className={styles.features}>
|
||||
<div className={styles.featureItem}>
|
||||
<span className={styles.featureIcon}>🚀</span>
|
||||
<span>高性能</span>
|
||||
</div>
|
||||
<div className={styles.featureItem}>
|
||||
<span className={styles.featureIcon}>🔒</span>
|
||||
<span>安全可靠</span>
|
||||
</div>
|
||||
<div className={styles.featureItem}>
|
||||
<span className={styles.featureIcon}>📦</span>
|
||||
<span>开箱即用</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</LoginForm>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div className={styles.footer}>
|
||||
<span>Kratos Admin © 2024 基于 Go Kratos 框架</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,43 @@
|
|||
import type { RequestOptions } from "@@/plugin-request/request";
|
||||
import type { RequestConfig } from "@umijs/max";
|
||||
import { message as toast } from "antd";
|
||||
import { history } from "@umijs/max";
|
||||
|
||||
// Define the structure of the expected response.
|
||||
interface ResponseStructure {
|
||||
code: number;
|
||||
reason?: string;
|
||||
message?: string;
|
||||
msg?: string;
|
||||
metadata?: Map<string, number>;
|
||||
}
|
||||
|
||||
const loginPath = "/user/login";
|
||||
|
||||
// Request configuration with error handling and interceptors.
|
||||
export const errorConfig: RequestConfig = {
|
||||
errorConfig: {
|
||||
errorThrower: (res) => {
|
||||
const { code, reason, message } = res as unknown as ResponseStructure;
|
||||
if (code != 200) {
|
||||
const error: any = new Error(message);
|
||||
error.info = { reason, message };
|
||||
const { code, reason, message, msg } = res as unknown as ResponseStructure;
|
||||
if (code !== 0 && code !== 200) {
|
||||
const error: any = new Error(message || msg);
|
||||
error.info = { reason, message: message || msg };
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
errorHandler: (error: any, opts: any) => {
|
||||
if (opts?.skipErrorHandler) throw error;
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userInfo');
|
||||
history.push(loginPath);
|
||||
return;
|
||||
}
|
||||
if (error.response?.data) {
|
||||
const errorInfo: ResponseStructure | undefined = error.response?.data;
|
||||
if (errorInfo) {
|
||||
const { message, reason } = errorInfo;
|
||||
toast.error(reason + ": " + message);
|
||||
const { message, msg, reason } = errorInfo;
|
||||
toast.error((reason ? reason + ": " : "") + (message || msg || "请求失败"));
|
||||
}
|
||||
} else if (error.response) {
|
||||
toast.error(`Response status: ${error.response.status}`);
|
||||
|
|
@ -40,12 +50,20 @@ export const errorConfig: RequestConfig = {
|
|||
},
|
||||
requestInterceptors: [
|
||||
(config: RequestOptions) => {
|
||||
// 添加token到请求头
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
'x-token': token,
|
||||
};
|
||||
}
|
||||
return config;
|
||||
},
|
||||
],
|
||||
responseInterceptors: [
|
||||
(response) => {
|
||||
if (response.status != 200) {
|
||||
if (response.status !== 200) {
|
||||
toast.error(`Request error: ${response.status}`);
|
||||
}
|
||||
return response;
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ export * from './dictionary';
|
|||
export * from './dictionaryDetail';
|
||||
export * from './operationRecord';
|
||||
export * from './casbin';
|
||||
export * from './params';
|
||||
export * from './systemConfig';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 系统参数管理 API 服务
|
||||
* 与 GVA 保持一致的接口定义
|
||||
*/
|
||||
import { request } from '@umijs/max';
|
||||
|
||||
/** 创建参数 POST /sysParams/createSysParams */
|
||||
export async function createSysParams(data: API.SysParams) {
|
||||
return request<API.BaseResult>('/api/sysParams/createSysParams', {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除参数 DELETE /sysParams/deleteSysParams */
|
||||
export async function deleteSysParams(params: { ID: number }) {
|
||||
return request<API.BaseResult>('/api/sysParams/deleteSysParams', {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 批量删除参数 DELETE /sysParams/deleteSysParamsByIds */
|
||||
export async function deleteSysParamsByIds(params: { 'IDs[]': number[] }) {
|
||||
return request<API.BaseResult>('/api/sysParams/deleteSysParamsByIds', {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 更新参数 PUT /sysParams/updateSysParams */
|
||||
export async function updateSysParams(data: API.SysParams) {
|
||||
return request<API.BaseResult>('/api/sysParams/updateSysParams', {
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据ID查询参数 GET /sysParams/findSysParams */
|
||||
export async function findSysParams(params: { ID: number }) {
|
||||
return request<API.SysParamsResult>('/api/sysParams/findSysParams', {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 分页获取参数列表 GET /sysParams/getSysParamsList */
|
||||
export async function getSysParamsList(params: API.PageParams & { name?: string; key?: string }) {
|
||||
return request<API.SysParamsListResult>('/api/sysParams/getSysParamsList', {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 根据key获取参数 GET /sysParams/getSysParam */
|
||||
export async function getSysParam(params: { key: string }) {
|
||||
return request<API.SysParamsResult>('/api/sysParams/getSysParam', {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* 系统配置 API 服务
|
||||
* 与 GVA 保持一致的接口定义
|
||||
*/
|
||||
import { request } from '@umijs/max';
|
||||
|
||||
/** 获取服务器信息 POST /system/getServerInfo */
|
||||
export async function getServerInfo() {
|
||||
return request<API.ServerInfoResult>('/api/system/getServerInfo', {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
|
@ -398,3 +398,71 @@ declare namespace API {
|
|||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// 系统参数
|
||||
type SysParams = {
|
||||
ID?: number;
|
||||
name?: string;
|
||||
key?: string;
|
||||
value?: string;
|
||||
desc?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
// 系统参数结果
|
||||
type SysParamsResult = {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: SysParams;
|
||||
};
|
||||
|
||||
// 系统参数列表结果
|
||||
type SysParamsListResult = {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: {
|
||||
list: SysParams[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
};
|
||||
|
||||
// 服务器信息
|
||||
type ServerInfo = {
|
||||
os: {
|
||||
goos: string;
|
||||
numCpu: number;
|
||||
compiler: string;
|
||||
goVersion: string;
|
||||
numGoroutine: number;
|
||||
};
|
||||
cpu: {
|
||||
cpus: number[];
|
||||
cores: number;
|
||||
};
|
||||
ram: {
|
||||
usedMb: number;
|
||||
totalMb: number;
|
||||
usedPercent: number;
|
||||
};
|
||||
disk: {
|
||||
mountPoint: string;
|
||||
usedMb: number;
|
||||
usedGb: number;
|
||||
totalMb: number;
|
||||
totalGb: number;
|
||||
usedPercent: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
// 服务器信息结果
|
||||
type ServerInfoResult = {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: {
|
||||
server: ServerInfo;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue