任务三
This commit is contained in:
parent
3c0f3b7165
commit
be3b6641a6
|
|
@ -11,73 +11,73 @@ server:
|
||||||
|
|
||||||
# jwt configuration
|
# jwt configuration
|
||||||
jwt:
|
jwt:
|
||||||
signing-key: kratos-admin
|
signing_key: kratos-admin
|
||||||
expires-time: 7d
|
expires_time: 7d
|
||||||
buffer-time: 1d
|
buffer_time: 1d
|
||||||
issuer: kratos-admin
|
issuer: kratos-admin
|
||||||
|
|
||||||
# system configuration
|
# system configuration
|
||||||
system:
|
system:
|
||||||
env: local
|
env: local
|
||||||
db-type: mysql
|
db_type: mysql
|
||||||
oss-type: local
|
oss_type: local
|
||||||
use-redis: false
|
use_redis: false
|
||||||
use-multipoint: false
|
use_multipoint: false
|
||||||
iplimit-count: 15000
|
iplimit_count: 15000
|
||||||
iplimit-time: 3600
|
iplimit_time: 3600
|
||||||
router-prefix: ""
|
router_prefix: ""
|
||||||
use-strict-auth: false
|
use_strict_auth: false
|
||||||
|
|
||||||
# captcha configuration
|
# captcha configuration
|
||||||
captcha:
|
captcha:
|
||||||
key-long: 6
|
key_long: 6
|
||||||
img-width: 240
|
img_width: 240
|
||||||
img-height: 80
|
img_height: 80
|
||||||
open-captcha: 0
|
open_captcha: 0
|
||||||
open-captcha-timeout: 3600
|
open_captcha_timeout: 3600
|
||||||
|
|
||||||
# mysql configuration
|
# mysql configuration
|
||||||
mysql:
|
mysql:
|
||||||
path: 127.0.0.1
|
path: 127.0.0.1
|
||||||
port: "3306"
|
port: "3306"
|
||||||
config: charset=utf8mb4&parseTime=True&loc=Local
|
config: charset=utf8mb4&parseTime=True&loc=Local
|
||||||
db-name: kratos_admin
|
db_name: spa
|
||||||
username: root
|
username: root
|
||||||
password: root
|
password: Xu950329.
|
||||||
max-idle-conns: 10
|
max_idle_conns: 10
|
||||||
max-open-conns: 100
|
max_open_conns: 100
|
||||||
log-mode: info
|
log_mode: info
|
||||||
log-zap: false
|
log_zap: false
|
||||||
|
|
||||||
# pgsql configuration
|
# pgsql configuration
|
||||||
pgsql:
|
pgsql:
|
||||||
path: ""
|
path: ""
|
||||||
port: ""
|
port: ""
|
||||||
config: ""
|
config: ""
|
||||||
db-name: ""
|
db_name: ""
|
||||||
username: ""
|
username: ""
|
||||||
password: ""
|
password: ""
|
||||||
max-idle-conns: 10
|
max_idle_conns: 10
|
||||||
max-open-conns: 100
|
max_open_conns: 100
|
||||||
log-mode: ""
|
log_mode: ""
|
||||||
log-zap: false
|
log_zap: false
|
||||||
|
|
||||||
# sqlite configuration
|
# sqlite configuration
|
||||||
sqlite:
|
sqlite:
|
||||||
path: ""
|
path: ""
|
||||||
db-name: ""
|
db_name: ""
|
||||||
max-idle-conns: 10
|
max_idle_conns: 10
|
||||||
max-open-conns: 100
|
max_open_conns: 100
|
||||||
log-mode: ""
|
log_mode: ""
|
||||||
log-zap: false
|
log_zap: false
|
||||||
|
|
||||||
# redis configuration
|
# redis configuration
|
||||||
redis:
|
redis:
|
||||||
use-cluster: false
|
use_cluster: false
|
||||||
addr: 127.0.0.1:6379
|
addr: 127.0.0.1:6379
|
||||||
password: ""
|
password: ""
|
||||||
db: 0
|
db: 0
|
||||||
cluster-addrs:
|
cluster_addrs:
|
||||||
- "127.0.0.1:7000"
|
- "127.0.0.1:7000"
|
||||||
- "127.0.0.1:7001"
|
- "127.0.0.1:7001"
|
||||||
- "127.0.0.1:7002"
|
- "127.0.0.1:7002"
|
||||||
|
|
@ -85,74 +85,74 @@ redis:
|
||||||
# local storage configuration
|
# local storage configuration
|
||||||
local:
|
local:
|
||||||
path: uploads/file
|
path: uploads/file
|
||||||
store-path: uploads/file
|
store_path: uploads/file
|
||||||
|
|
||||||
# qiniu configuration
|
# qiniu configuration
|
||||||
qiniu:
|
qiniu:
|
||||||
zone: ZoneHuaDong
|
zone: ZoneHuaDong
|
||||||
bucket: ""
|
bucket: ""
|
||||||
img-path: ""
|
img_path: ""
|
||||||
use-https: false
|
use_https: false
|
||||||
access-key: ""
|
access_key: ""
|
||||||
secret-key: ""
|
secret_key: ""
|
||||||
use-cdn-domains: false
|
use_cdn_domains: false
|
||||||
|
|
||||||
# minio configuration
|
# minio configuration
|
||||||
minio:
|
minio:
|
||||||
endpoint: ""
|
endpoint: ""
|
||||||
access-key-id: ""
|
access_key_id: ""
|
||||||
access-key-secret: ""
|
access_key_secret: ""
|
||||||
bucket-name: ""
|
bucket_name: ""
|
||||||
use-ssl: false
|
use_ssl: false
|
||||||
base-path: ""
|
base_path: ""
|
||||||
bucket-url: ""
|
bucket_url: ""
|
||||||
|
|
||||||
# aliyun oss configuration
|
# aliyun oss configuration
|
||||||
aliyun-oss:
|
aliyun_oss:
|
||||||
endpoint: ""
|
endpoint: ""
|
||||||
access-key-id: ""
|
access_key_id: ""
|
||||||
access-key-secret: ""
|
access_key_secret: ""
|
||||||
bucket-name: ""
|
bucket_name: ""
|
||||||
bucket-url: ""
|
bucket_url: ""
|
||||||
base-path: ""
|
base_path: ""
|
||||||
|
|
||||||
# tencent cos configuration
|
# tencent cos configuration
|
||||||
tencent-cos:
|
tencent_cos:
|
||||||
bucket: ""
|
bucket: ""
|
||||||
region: ""
|
region: ""
|
||||||
secret-id: ""
|
secret_id: ""
|
||||||
secret-key: ""
|
secret_key: ""
|
||||||
base-url: ""
|
base_url: ""
|
||||||
path-prefix: ""
|
path_prefix: ""
|
||||||
|
|
||||||
# aws s3 configuration
|
# aws s3 configuration
|
||||||
aws-s3:
|
aws_s3:
|
||||||
bucket: ""
|
bucket: ""
|
||||||
region: ""
|
region: ""
|
||||||
endpoint: ""
|
endpoint: ""
|
||||||
s3-force-path-style: false
|
s3_force_path_style: false
|
||||||
disable-ssl: false
|
disable_ssl: false
|
||||||
secret-id: ""
|
secret_id: ""
|
||||||
secret-key: ""
|
secret_key: ""
|
||||||
base-url: ""
|
base_url: ""
|
||||||
path-prefix: ""
|
path_prefix: ""
|
||||||
|
|
||||||
# cloudflare r2 configuration
|
# cloudflare r2 configuration
|
||||||
cloudflare-r2:
|
cloudflare_r2:
|
||||||
bucket: ""
|
bucket: ""
|
||||||
base-url: ""
|
base_url: ""
|
||||||
path: uploads
|
path: uploads
|
||||||
account-id: ""
|
account_id: ""
|
||||||
access-key-id: ""
|
access_key_id: ""
|
||||||
secret-access-key: ""
|
secret_access_key: ""
|
||||||
|
|
||||||
# huawei obs configuration
|
# huawei obs configuration
|
||||||
huawei-obs:
|
huawei_obs:
|
||||||
path: ""
|
path: ""
|
||||||
bucket: ""
|
bucket: ""
|
||||||
endpoint: ""
|
endpoint: ""
|
||||||
access-key: ""
|
access_key: ""
|
||||||
secret-key: ""
|
secret_key: ""
|
||||||
|
|
||||||
# email configuration
|
# email configuration
|
||||||
email:
|
email:
|
||||||
|
|
@ -160,7 +160,7 @@ email:
|
||||||
port: 465
|
port: 465
|
||||||
from: ""
|
from: ""
|
||||||
host: ""
|
host: ""
|
||||||
is-ssl: true
|
is_ssl: true
|
||||||
secret: ""
|
secret: ""
|
||||||
nickname: ""
|
nickname: ""
|
||||||
|
|
||||||
|
|
@ -172,11 +172,11 @@ excel:
|
||||||
cors:
|
cors:
|
||||||
mode: allow-all
|
mode: allow-all
|
||||||
whitelist:
|
whitelist:
|
||||||
- allow-origin: "*"
|
- allow_origin: "*"
|
||||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id
|
allow_headers: Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id
|
||||||
allow-methods: POST,GET,OPTIONS,DELETE,PUT
|
allow_methods: POST,GET,OPTIONS,DELETE,PUT
|
||||||
expose-headers: Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type
|
expose_headers: Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type
|
||||||
allow-credentials: true
|
allow_credentials: true
|
||||||
|
|
||||||
# zap logger configuration
|
# zap logger configuration
|
||||||
zap:
|
zap:
|
||||||
|
|
@ -184,8 +184,8 @@ zap:
|
||||||
format: console
|
format: console
|
||||||
prefix: "[kratos-admin]"
|
prefix: "[kratos-admin]"
|
||||||
director: log
|
director: log
|
||||||
show-line: true
|
show_line: true
|
||||||
encode-level: LowercaseColorLevelEncoder
|
encode_level: LowercaseColorLevelEncoder
|
||||||
stacktrace-key: stacktrace
|
stacktrace_key: stacktrace
|
||||||
log-in-console: true
|
log_in_console: true
|
||||||
retention-day: -1
|
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/qiniu/go-sdk/v7 v7.25.2
|
||||||
github.com/redis/go-redis/v9 v9.7.0
|
github.com/redis/go-redis/v9 v9.7.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
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/tencentyun/cos-go-sdk-v5 v0.7.60
|
||||||
github.com/xuri/excelize/v2 v2.9.0
|
github.com/xuri/excelize/v2 v2.9.0
|
||||||
go.einride.tech/aip v0.78.0
|
go.einride.tech/aip v0.78.0
|
||||||
go.uber.org/automaxprocs v1.6.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
golang.org/x/crypto v0.38.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
|
google.golang.org/protobuf v1.36.10
|
||||||
gorm.io/driver/mysql v1.5.7
|
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/gen v0.3.27
|
||||||
gorm.io/gorm v1.30.0
|
gorm.io/gorm v1.30.0
|
||||||
gorm.io/plugin/dbresolver v1.6.2
|
gorm.io/plugin/dbresolver v1.6.2
|
||||||
|
|
@ -73,6 +70,7 @@ require (
|
||||||
github.com/go-kratos/aegis v0.2.0 // indirect
|
github.com/go-kratos/aegis v0.2.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // 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/go-playground/form/v4 v4.2.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.4 // indirect
|
||||||
github.com/gofrs/flock v0.8.1 // indirect
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
|
|
@ -89,19 +87,24 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // 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-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/microsoft/go-mssqldb v1.6.0 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/mozillazg/go-httpheader v0.2.1 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||||
github.com/rs/xid v1.6.0 // 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/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // 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/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric 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/text v0.25.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.30.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/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
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/datatypes v1.2.4 // 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/driver/sqlserver v1.5.3 // indirect
|
||||||
gorm.io/hints v1.1.0 // indirect
|
gorm.io/hints v1.1.0 // indirect
|
||||||
modernc.org/fileutil v1.0.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/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 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
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 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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=
|
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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
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.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.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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
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/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 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
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=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
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/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 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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/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 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o=
|
||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
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 h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
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=
|
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 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
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/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 h1:nbnM/OuGt5Pyz/r2KOxB6Hp+ey2e0+MNnfIPBtY45pY=
|
||||||
go.einride.tech/aip v0.78.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig=
|
go.einride.tech/aip v0.78.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
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/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-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-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-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-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-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-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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.
|
// 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"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"kra/internal/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Api API实体
|
// Api API实体
|
||||||
|
|
@ -71,12 +73,12 @@ type RouterInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApiUsecase 创建API用例
|
// 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{
|
return &ApiUsecase{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
casbinRepo: casbinRepo,
|
casbinRepo: casbinRepo,
|
||||||
authRepo: authRepo,
|
authRepo: authRepo,
|
||||||
useStrictAuth: useStrictAuth,
|
useStrictAuth: bool(useStrictAuth),
|
||||||
routers: make([]RouterInfo, 0),
|
routers: make([]RouterInfo, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"kra/internal/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -84,11 +86,11 @@ type AuthorityUsecase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthorityUsecase 创建角色用例
|
// NewAuthorityUsecase 创建角色用例
|
||||||
func NewAuthorityUsecase(repo AuthorityRepo, casbinRepo CasbinRepo, useStrictAuth bool) *AuthorityUsecase {
|
func NewAuthorityUsecase(repo AuthorityRepo, casbinRepo CasbinRepo, useStrictAuth conf.UseStrictAuth) *AuthorityUsecase {
|
||||||
return &AuthorityUsecase{
|
return &AuthorityUsecase{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
casbinRepo: casbinRepo,
|
casbinRepo: casbinRepo,
|
||||||
useStrictAuth: useStrictAuth,
|
useStrictAuth: bool(useStrictAuth),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"kra/internal/conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseMenu 基础菜单实体
|
// BaseMenu 基础菜单实体
|
||||||
|
|
@ -84,11 +86,11 @@ type MenuUsecase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMenuUsecase 创建菜单用例
|
// NewMenuUsecase 创建菜单用例
|
||||||
func NewMenuUsecase(repo MenuRepo, authRepo AuthorityRepo, useStrictAuth bool) *MenuUsecase {
|
func NewMenuUsecase(repo MenuRepo, authRepo AuthorityRepo, useStrictAuth conf.UseStrictAuth) *MenuUsecase {
|
||||||
return &MenuUsecase{
|
return &MenuUsecase{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
authRepo: authRepo,
|
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"
|
"fmt"
|
||||||
"kra/internal/conf"
|
"kra/internal/conf"
|
||||||
datasystem "kra/internal/data/system"
|
datasystem "kra/internal/data/system"
|
||||||
|
pkgcasbin "kra/pkg/casbin"
|
||||||
|
|
||||||
|
"github.com/casbin/casbin/v2"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
|
|
@ -16,8 +18,18 @@ import (
|
||||||
var ProviderSet = wire.NewSet(
|
var ProviderSet = wire.NewSet(
|
||||||
NewDB,
|
NewDB,
|
||||||
NewData,
|
NewData,
|
||||||
|
NewCasbinEnforcer,
|
||||||
datasystem.NewUserRepo,
|
datasystem.NewUserRepo,
|
||||||
datasystem.NewApiRepo,
|
datasystem.NewApiRepo,
|
||||||
|
datasystem.NewAuthorityRepo,
|
||||||
|
datasystem.NewAuthorityBtnRepo,
|
||||||
|
datasystem.NewCasbinRepo,
|
||||||
|
datasystem.NewMenuRepo,
|
||||||
|
datasystem.NewDictionaryRepo,
|
||||||
|
datasystem.NewDictionaryDetailRepo,
|
||||||
|
datasystem.NewJwtBlacklistRepo,
|
||||||
|
datasystem.NewOperationRecordRepo,
|
||||||
|
datasystem.NewParamsRepo,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data 数据层包装器
|
// Data 数据层包装器
|
||||||
|
|
@ -49,3 +61,8 @@ func NewDB(c *conf.Mysql) (*gorm.DB, error) {
|
||||||
}
|
}
|
||||||
return db, nil
|
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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"kra/internal/conf"
|
"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/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.
|
// NewHTTPServer new an HTTP server.
|
||||||
func NewHTTPServer(c *conf.Server) *http.Server {
|
func NewHTTPServer(
|
||||||
var opts = []http.ServerOption{
|
c *conf.Server,
|
||||||
http.Middleware(
|
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(),
|
recovery.Recovery(),
|
||||||
|
// JWT认证中间件(排除公开路由)
|
||||||
|
selector.Server(
|
||||||
|
middleware.JWTAuthSimple(jwtPkg),
|
||||||
|
).Match(newWhiteListMatcher()).Build(),
|
||||||
),
|
),
|
||||||
|
khttp.ResponseEncoder(response.Encoder),
|
||||||
|
khttp.ErrorEncoder(response.ErrorEncoder),
|
||||||
}
|
}
|
||||||
if c.Http.Network != "" {
|
if c.Http.Network != "" {
|
||||||
opts = append(opts, http.Network(c.Http.Network))
|
opts = append(opts, khttp.Network(c.Http.Network))
|
||||||
}
|
}
|
||||||
if c.Http.Addr != "" {
|
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 {
|
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
|
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
|
package service
|
||||||
|
|
||||||
import "github.com/google/wire"
|
import (
|
||||||
|
"kra/internal/service/system"
|
||||||
|
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
// ProviderSet is service providers.
|
// 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
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
|
types "kra/internal/service/types/system"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/errors"
|
"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服务
|
// ApiService API服务
|
||||||
|
|
@ -20,45 +19,30 @@ func NewApiService(uc *system.ApiUsecase) *ApiService {
|
||||||
return &ApiService{uc: uc}
|
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
|
// 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{
|
api := &system.Api{
|
||||||
Path: req.Path,
|
Path: req.Path,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
ApiGroup: req.ApiGroup,
|
ApiGroup: req.ApiGroup,
|
||||||
Method: req.Method,
|
Method: req.Method,
|
||||||
}
|
}
|
||||||
return s.uc.CreateApi(ctx, api)
|
if err := s.uc.CreateApi(ctx, api); err != nil {
|
||||||
}
|
return errors.BadRequest("CREATE_FAILED", err.Error())
|
||||||
|
}
|
||||||
// UpdateApiRequest 更新API请求
|
return ctx.Result(200, nil)
|
||||||
type UpdateApiRequest struct {
|
|
||||||
ID uint `json:"ID"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
ApiGroup string `json:"apiGroup"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateApi 更新API
|
// 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{
|
api := &system.Api{
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Path: req.Path,
|
Path: req.Path,
|
||||||
|
|
@ -66,55 +50,55 @@ func (s *ApiService) UpdateApi(ctx context.Context, req *UpdateApiRequest) error
|
||||||
ApiGroup: req.ApiGroup,
|
ApiGroup: req.ApiGroup,
|
||||||
Method: req.Method,
|
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
|
// DeleteApi 删除API
|
||||||
func (s *ApiService) DeleteApi(ctx context.Context, id uint) error {
|
func (s *ApiService) DeleteApi(ctx khttp.Context) error {
|
||||||
return s.uc.DeleteApi(ctx, id)
|
var req types.DeleteApiRequest
|
||||||
}
|
if err := ctx.Bind(&req); err != nil {
|
||||||
|
return err
|
||||||
// DeleteApisByIdsRequest 批量删除API请求
|
}
|
||||||
type DeleteApisByIdsRequest struct {
|
if err := s.uc.DeleteApi(ctx, req.ID); err != nil {
|
||||||
Ids []uint `json:"ids"`
|
return errors.BadRequest("DELETE_FAILED", err.Error())
|
||||||
|
}
|
||||||
|
return ctx.Result(200, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteApisByIds 批量删除API
|
// DeleteApisByIds 批量删除API
|
||||||
func (s *ApiService) DeleteApisByIds(ctx context.Context, req *DeleteApisByIdsRequest) error {
|
func (s *ApiService) DeleteApisByIds(ctx khttp.Context) error {
|
||||||
return s.uc.DeleteApisByIds(ctx, req.Ids)
|
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
|
// GetApiById 根据ID获取API
|
||||||
func (s *ApiService) GetApiById(ctx context.Context, id uint) (*ApiInfo, error) {
|
func (s *ApiService) GetApiById(ctx khttp.Context) error {
|
||||||
api, err := s.uc.GetApiById(ctx, id)
|
var req types.GetApiByIdRequest
|
||||||
if err != nil {
|
if err := ctx.Bind(&req); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
return toApiInfo(api), nil
|
api, err := s.uc.GetApiById(ctx, req.ID)
|
||||||
}
|
if err != nil {
|
||||||
|
return errors.NotFound("NOT_FOUND", err.Error())
|
||||||
// GetApiListRequest 获取API列表请求
|
}
|
||||||
type GetApiListRequest struct {
|
return ctx.Result(200, &types.GetApiByIdReply{Api: toTypesApiInfo(api)})
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetApiList 获取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)
|
filters := make(map[string]string)
|
||||||
if req.Path != "" {
|
if req.Path != "" {
|
||||||
filters["path"] = req.Path
|
filters["path"] = req.Path
|
||||||
|
|
@ -128,94 +112,71 @@ func (s *ApiService) GetApiList(ctx context.Context, req *GetApiListRequest) (*G
|
||||||
if req.ApiGroup != "" {
|
if req.ApiGroup != "" {
|
||||||
filters["api_group"] = req.ApiGroup
|
filters["api_group"] = req.ApiGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
apis, total, err := s.uc.GetAPIInfoList(ctx, req.Page, req.PageSize, filters, req.OrderKey, req.Desc)
|
apis, total, err := s.uc.GetAPIInfoList(ctx, req.Page, req.PageSize, filters, req.OrderKey, req.Desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
list := make([]types.ApiInfo, len(apis))
|
||||||
list := make([]*ApiInfo, len(apis))
|
|
||||||
for i, api := range apis {
|
for i, api := range apis {
|
||||||
list[i] = toApiInfo(api)
|
list[i] = toTypesApiInfo(api)
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.GetApiListReply{List: list, Total: total, Page: req.Page, PageSize: req.PageSize})
|
||||||
return &GetApiListResponse{
|
|
||||||
List: list,
|
|
||||||
Total: total,
|
|
||||||
Page: req.Page,
|
|
||||||
PageSize: req.PageSize,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllApis 获取所有API
|
// 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)
|
apis, err := s.uc.GetAllApis(ctx, authorityID)
|
||||||
if err != nil {
|
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 {
|
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分组
|
// GetApiGroups 获取API分组
|
||||||
func (s *ApiService) GetApiGroups(ctx context.Context) ([]string, map[string]string, error) {
|
func (s *ApiService) GetApiGroups(ctx khttp.Context) error {
|
||||||
return s.uc.GetApiGroups(ctx)
|
groups, apiGroupMap, err := s.uc.GetApiGroups(ctx)
|
||||||
}
|
if err != nil {
|
||||||
|
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||||
// SyncApiResponse 同步API响应
|
}
|
||||||
type SyncApiResponse struct {
|
return ctx.Result(200, &types.GetApiGroupsReply{Groups: groups, ApiGroupMap: apiGroupMap})
|
||||||
NewApis []*ApiInfo `json:"newApis"`
|
|
||||||
DeleteApis []*ApiInfo `json:"deleteApis"`
|
|
||||||
IgnoreApis []*ApiInfo `json:"ignoreApis"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncApi 同步API
|
// 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)
|
newApis, deleteApis, ignoreApis, err := s.uc.SyncApi(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return errors.InternalServer("SYNC_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.SyncApiReply{
|
||||||
resp := &SyncApiResponse{
|
NewApis: toTypesApiInfoList(newApis),
|
||||||
NewApis: make([]*ApiInfo, len(newApis)),
|
DeleteApis: toTypesApiInfoList(deleteApis),
|
||||||
DeleteApis: make([]*ApiInfo, len(deleteApis)),
|
IgnoreApis: toTypesApiInfoList(ignoreApis),
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IgnoreApi 忽略API
|
// IgnoreApi 忽略API
|
||||||
func (s *ApiService) IgnoreApi(ctx context.Context, req *IgnoreApiRequest) error {
|
func (s *ApiService) IgnoreApi(ctx khttp.Context) error {
|
||||||
return s.uc.IgnoreApi(ctx, req.Path, req.Method, req.Flag)
|
var req types.IgnoreApiRequest
|
||||||
}
|
if err := ctx.Bind(&req); err != nil {
|
||||||
|
return err
|
||||||
// EnterSyncApiRequest 确认同步API请求
|
}
|
||||||
type EnterSyncApiRequest struct {
|
if err := s.uc.IgnoreApi(ctx, req.Path, req.Method, req.Flag); err != nil {
|
||||||
NewApis []*ApiInfo `json:"newApis"`
|
return errors.BadRequest("IGNORE_FAILED", err.Error())
|
||||||
DeleteApis []*ApiInfo `json:"deleteApis"`
|
}
|
||||||
|
return ctx.Result(200, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnterSyncApi 确认同步API
|
// 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))
|
newApis := make([]*system.Api, len(req.NewApis))
|
||||||
for i, api := range req.NewApis {
|
for i, api := range req.NewApis {
|
||||||
newApis[i] = &system.Api{
|
newApis[i] = &system.Api{
|
||||||
|
|
@ -233,17 +194,26 @@ func (s *ApiService) EnterSyncApi(ctx context.Context, req *EnterSyncApiRequest)
|
||||||
Method: api.Method,
|
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缓存
|
// FreshCasbin 刷新Casbin缓存
|
||||||
func (s *ApiService) FreshCasbin() error {
|
func (s *ApiService) FreshCasbin(ctx khttp.Context) error {
|
||||||
return s.uc.FreshCasbin()
|
if err := s.uc.FreshCasbin(); err != nil {
|
||||||
|
return errors.InternalServer("FRESH_FAILED", err.Error())
|
||||||
|
}
|
||||||
|
return ctx.Result(200, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换函数
|
// toTypesApiInfo 转换API信息为types格式
|
||||||
func toApiInfo(api *system.Api) *ApiInfo {
|
func toTypesApiInfo(api *system.Api) types.ApiInfo {
|
||||||
return &ApiInfo{
|
if api == nil {
|
||||||
|
return types.ApiInfo{}
|
||||||
|
}
|
||||||
|
return types.ApiInfo{
|
||||||
ID: api.ID,
|
ID: api.ID,
|
||||||
Path: api.Path,
|
Path: api.Path,
|
||||||
Description: api.Description,
|
Description: api.Description,
|
||||||
|
|
@ -252,147 +222,32 @@ func toApiInfo(api *system.Api) *ApiInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// toTypesApiInfoList 批量转换API信息
|
||||||
func (s *ApiService) RegisterRoutes(srv *http.Server) {
|
func toTypesApiInfoList(apis []*system.Api) []types.ApiInfo {
|
||||||
r := srv.Route("/")
|
list := make([]types.ApiInfo, len(apis))
|
||||||
|
for i, api := range apis {
|
||||||
r.POST("/api/createApi", s.handleCreateApi)
|
list[i] = toTypesApiInfo(api)
|
||||||
r.POST("/api/updateApi", s.handleUpdateApi)
|
}
|
||||||
r.POST("/api/deleteApi", s.handleDeleteApi)
|
return list
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Handlers
|
// RegisterRoutes 注册API管理路由
|
||||||
func (s *ApiService) handleCreateApi(ctx http.Context) error {
|
func (s *ApiService) RegisterRoutes(rg *RouterGroup) {
|
||||||
var req CreateApiRequest
|
// 公开路由
|
||||||
if err := ctx.Bind(&req); err != nil {
|
rg.Public.GET("/api/freshCasbin", s.FreshCasbin)
|
||||||
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": "创建成功"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ApiService) handleUpdateApi(ctx http.Context) error {
|
// 私有路由 + 操作记录(写操作)
|
||||||
var req UpdateApiRequest
|
rg.PrivateWithRecord.GET("/api/getApiGroups", s.GetApiGroups)
|
||||||
if err := ctx.Bind(&req); err != nil {
|
rg.PrivateWithRecord.GET("/api/syncApi", s.SyncApi)
|
||||||
return err
|
rg.PrivateWithRecord.POST("/api/ignoreApi", s.IgnoreApi)
|
||||||
}
|
rg.PrivateWithRecord.POST("/api/enterSyncApi", s.EnterSyncApi)
|
||||||
if err := s.UpdateApi(ctx, &req); err != nil {
|
rg.PrivateWithRecord.POST("/api/createApi", s.CreateApi)
|
||||||
return errors.BadRequest("UPDATE_FAILED", err.Error())
|
rg.PrivateWithRecord.POST("/api/deleteApi", s.DeleteApi)
|
||||||
}
|
rg.PrivateWithRecord.POST("/api/getApiById", s.GetApiById)
|
||||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "修改成功"})
|
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 {
|
rg.Private.POST("/api/getAllApis", s.GetAllApis)
|
||||||
ID uint `json:"ID"`
|
rg.Private.POST("/api/getApiList", s.GetApiList)
|
||||||
}
|
|
||||||
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": "刷新成功"})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
|
types "kra/internal/service/types/system"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/errors"
|
"github.com/go-kratos/kratos/v2/errors"
|
||||||
"github.com/go-kratos/kratos/v2/transport/http"
|
"github.com/go-kratos/kratos/v2/transport/http"
|
||||||
|
|
@ -26,12 +27,12 @@ func NewAuthorityService(uc *system.AuthorityUsecase, casbin system.CasbinRepo)
|
||||||
|
|
||||||
// AuthorityInfo 角色信息
|
// AuthorityInfo 角色信息
|
||||||
type AuthorityFullInfo struct {
|
type AuthorityFullInfo struct {
|
||||||
AuthorityId uint `json:"authorityId"`
|
AuthorityId uint `json:"authorityId"`
|
||||||
AuthorityName string `json:"authorityName"`
|
AuthorityName string `json:"authorityName"`
|
||||||
ParentId *uint `json:"parentId,omitempty"`
|
ParentId *uint `json:"parentId,omitempty"`
|
||||||
DefaultRouter string `json:"defaultRouter"`
|
DefaultRouter string `json:"defaultRouter"`
|
||||||
DataAuthorityId []*AuthorityInfo `json:"dataAuthorityId,omitempty"`
|
DataAuthorityId []*types.AuthorityInfo `json:"dataAuthorityId,omitempty"`
|
||||||
Children []*AuthorityFullInfo `json:"children,omitempty"`
|
Children []*AuthorityFullInfo `json:"children,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAuthorityRequest 创建角色请求
|
// CreateAuthorityRequest 创建角色请求
|
||||||
|
|
@ -172,9 +173,9 @@ func toAuthorityFullInfoList(list []*system.AuthorityFull) []*AuthorityFullInfo
|
||||||
}
|
}
|
||||||
// DataAuthorityId
|
// DataAuthorityId
|
||||||
if len(auth.DataAuthorityId) > 0 {
|
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 {
|
for j, da := range auth.DataAuthorityId {
|
||||||
result[i].DataAuthorityId[j] = &AuthorityInfo{
|
result[i].DataAuthorityId[j] = &types.AuthorityInfo{
|
||||||
AuthorityId: da.AuthorityId,
|
AuthorityId: da.AuthorityId,
|
||||||
AuthorityName: da.AuthorityName,
|
AuthorityName: da.AuthorityName,
|
||||||
ParentId: da.ParentId,
|
ParentId: da.ParentId,
|
||||||
|
|
@ -187,15 +188,16 @@ func toAuthorityFullInfoList(list []*system.AuthorityFull) []*AuthorityFullInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *AuthorityService) RegisterRoutes(srv *http.Server) {
|
func (s *AuthorityService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 私有路由 + 操作记录(写操作)
|
||||||
|
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)
|
rg.Private.POST("/authority/getAuthorityList", s.handleGetAuthorityList)
|
||||||
r.PUT("/authority/updateAuthority", s.handleUpdateAuthority)
|
|
||||||
r.POST("/authority/deleteAuthority", s.handleDeleteAuthority)
|
|
||||||
r.POST("/authority/getAuthorityList", s.handleGetAuthorityList)
|
|
||||||
r.POST("/authority/setDataAuthority", s.handleSetDataAuthority)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Handlers
|
// HTTP Handlers
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,11 @@ type SetAuthorityBtnRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *AuthorityBtnService) RegisterRoutes(srv *http.Server) {
|
func (s *AuthorityBtnService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 角色按钮权限(不需要操作记录)
|
||||||
r.POST("/authorityBtn/getAuthorityBtn", s.handleGetAuthorityBtn)
|
rg.Private.POST("/authorityBtn/getAuthorityBtn", s.handleGetAuthorityBtn)
|
||||||
r.POST("/authorityBtn/setAuthorityBtn", s.handleSetAuthorityBtn)
|
rg.Private.POST("/authorityBtn/setAuthorityBtn", s.handleSetAuthorityBtn)
|
||||||
r.POST("/authorityBtn/canRemoveAuthorityBtn", s.handleCanRemoveAuthorityBtn)
|
rg.Private.POST("/authorityBtn/canRemoveAuthorityBtn", s.handleCanRemoveAuthorityBtn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthorityBtnService) handleGetAuthorityBtn(ctx http.Context) error {
|
func (s *AuthorityBtnService) handleGetAuthorityBtn(ctx http.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,30 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"kra/internal/conf"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/transport/http"
|
"github.com/go-kratos/kratos/v2/transport/http"
|
||||||
"github.com/mojocn/base64Captcha"
|
"github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CaptchaService 验证码服务
|
// CaptchaService 验证码服务
|
||||||
type CaptchaService struct {
|
type CaptchaService struct {
|
||||||
config CaptchaConfig
|
keyLong int
|
||||||
store base64Captcha.Store
|
imgWidth int
|
||||||
blackCache *BlackCache
|
imgHeight int
|
||||||
|
openCaptcha int // 防爆次数,0表示关闭
|
||||||
|
openCaptchaTimeOut int // 缓存超时时间(秒)
|
||||||
|
store base64Captcha.Store
|
||||||
|
blackCache *BlackCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptchaConfig 验证码配置
|
// CaptchaConfig 验证码配置(保留用于兼容)
|
||||||
type CaptchaConfig struct {
|
type CaptchaConfig struct {
|
||||||
KeyLong int
|
KeyLong int
|
||||||
ImgWidth int
|
ImgWidth int
|
||||||
ImgHeight int
|
ImgHeight int
|
||||||
OpenCaptcha int // 防爆次数,0表示关闭
|
OpenCaptcha int
|
||||||
OpenCaptchaTimeOut int // 缓存超时时间(秒)
|
OpenCaptchaTimeOut int
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlackCache 黑名单缓存
|
// BlackCache 黑名单缓存
|
||||||
|
|
@ -86,21 +92,22 @@ type CaptchaResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCaptchaService 创建验证码服务
|
// NewCaptchaService 创建验证码服务
|
||||||
func NewCaptchaService(config CaptchaConfig, store base64Captcha.Store) *CaptchaService {
|
func NewCaptchaService(c *conf.Captcha) *CaptchaService {
|
||||||
if store == nil {
|
|
||||||
store = base64Captcha.DefaultMemStore
|
|
||||||
}
|
|
||||||
return &CaptchaService{
|
return &CaptchaService{
|
||||||
config: config,
|
keyLong: int(c.KeyLong),
|
||||||
store: store,
|
imgWidth: int(c.ImgWidth),
|
||||||
blackCache: NewBlackCache(),
|
imgHeight: int(c.ImgHeight),
|
||||||
|
openCaptcha: int(c.OpenCaptcha),
|
||||||
|
openCaptchaTimeOut: int(c.OpenCaptchaTimeout),
|
||||||
|
store: base64Captcha.DefaultMemStore,
|
||||||
|
blackCache: NewBlackCache(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *CaptchaService) RegisterRoutes(srv *http.Server) {
|
func (s *CaptchaService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 验证码(公开路由,无需认证)
|
||||||
r.POST("/base/captcha", s.handleCaptcha)
|
rg.Public.POST("/base/captcha", s.handleCaptcha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||||
|
|
@ -108,8 +115,8 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||||
clientIP := ctx.Request().RemoteAddr
|
clientIP := ctx.Request().RemoteAddr
|
||||||
|
|
||||||
// 判断验证码是否开启
|
// 判断验证码是否开启
|
||||||
openCaptcha := s.config.OpenCaptcha
|
openCaptcha := s.openCaptcha
|
||||||
openCaptchaTimeOut := s.config.OpenCaptchaTimeOut
|
openCaptchaTimeOut := s.openCaptchaTimeOut
|
||||||
|
|
||||||
v, ok := s.blackCache.Get(clientIP)
|
v, ok := s.blackCache.Get(clientIP)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -123,9 +130,9 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||||
|
|
||||||
// 生成验证码
|
// 生成验证码
|
||||||
driver := base64Captcha.NewDriverDigit(
|
driver := base64Captcha.NewDriverDigit(
|
||||||
s.config.ImgHeight,
|
s.imgHeight,
|
||||||
s.config.ImgWidth,
|
s.imgWidth,
|
||||||
s.config.KeyLong,
|
s.keyLong,
|
||||||
0.7,
|
0.7,
|
||||||
80,
|
80,
|
||||||
)
|
)
|
||||||
|
|
@ -144,7 +151,7 @@ func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||||
"data": CaptchaResponse{
|
"data": CaptchaResponse{
|
||||||
CaptchaId: id,
|
CaptchaId: id,
|
||||||
PicPath: b64s,
|
PicPath: b64s,
|
||||||
CaptchaLength: s.config.KeyLong,
|
CaptchaLength: s.keyLong,
|
||||||
OpenCaptcha: oc,
|
OpenCaptcha: oc,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
|
"kra/internal/conf"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
|
|
||||||
kerrors "github.com/go-kratos/kratos/v2/errors"
|
kerrors "github.com/go-kratos/kratos/v2/errors"
|
||||||
|
|
@ -20,12 +21,12 @@ type CasbinService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCasbinService 创建Casbin服务
|
// 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{
|
return &CasbinService{
|
||||||
casbinRepo: casbinRepo,
|
casbinRepo: casbinRepo,
|
||||||
authUc: authUc,
|
authUc: authUc,
|
||||||
apiUc: apiUc,
|
apiUc: apiUc,
|
||||||
useStrictAuth: useStrictAuth,
|
useStrictAuth: bool(useStrictAuth),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,10 +82,12 @@ func (s *CasbinService) GetPolicyPathByAuthorityId(authorityId uint) []system.Ca
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *CasbinService) RegisterRoutes(srv *http.Server) {
|
func (s *CasbinService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 私有路由 + 操作记录(写操作)
|
||||||
r.POST("/casbin/UpdateCasbin", s.handleUpdateCasbin)
|
rg.PrivateWithRecord.POST("/casbin/updateCasbin", s.handleUpdateCasbin)
|
||||||
r.POST("/casbin/getPolicyPathByAuthorityId", s.handleGetPolicyPath)
|
|
||||||
|
// 私有路由(读操作,不记录)
|
||||||
|
rg.Private.POST("/casbin/getPolicyPathByAuthorityId", s.handleGetPolicyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CasbinService) handleUpdateCasbin(ctx http.Context) error {
|
func (s *CasbinService) handleUpdateCasbin(ctx http.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -365,28 +365,30 @@ func toDictionaryDetailInfoList(list []*system.DictionaryDetail) []*DictionaryDe
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *DictionaryService) RegisterRoutes(srv *http.Server) {
|
func (s *DictionaryService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 字典接口 - 写操作(带操作记录)
|
||||||
|
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)
|
rg.Private.GET("/sysDictionary/findSysDictionary", s.handleGetDictionary)
|
||||||
r.DELETE("/sysDictionary/deleteSysDictionary", s.handleDeleteDictionary)
|
rg.Private.GET("/sysDictionary/getSysDictionaryList", s.handleGetDictionaryList)
|
||||||
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)
|
|
||||||
|
|
||||||
// 字典详情接口
|
// 字典详情接口 - 写操作(带操作记录)
|
||||||
r.POST("/sysDictionaryDetail/createSysDictionaryDetail", s.handleCreateDictionaryDetail)
|
rg.PrivateWithRecord.POST("/sysDictionaryDetail/createSysDictionaryDetail", s.handleCreateDictionaryDetail)
|
||||||
r.DELETE("/sysDictionaryDetail/deleteSysDictionaryDetail", s.handleDeleteDictionaryDetail)
|
rg.PrivateWithRecord.DELETE("/sysDictionaryDetail/deleteSysDictionaryDetail", s.handleDeleteDictionaryDetail)
|
||||||
r.PUT("/sysDictionaryDetail/updateSysDictionaryDetail", s.handleUpdateDictionaryDetail)
|
rg.PrivateWithRecord.PUT("/sysDictionaryDetail/updateSysDictionaryDetail", s.handleUpdateDictionaryDetail)
|
||||||
r.GET("/sysDictionaryDetail/findSysDictionaryDetail", s.handleGetDictionaryDetail)
|
|
||||||
r.GET("/sysDictionaryDetail/getSysDictionaryDetailList", s.handleGetDictionaryDetailList)
|
// 字典详情接口 - 读操作(不记录)
|
||||||
r.GET("/sysDictionaryDetail/getDictionaryTreeList", s.handleGetDictionaryTreeList)
|
rg.Private.GET("/sysDictionaryDetail/findSysDictionaryDetail", s.handleGetDictionaryDetail)
|
||||||
r.POST("/sysDictionaryDetail/getDictionaryDetailsByParent", s.handleGetDictionaryDetailsByParent)
|
rg.Private.GET("/sysDictionaryDetail/getSysDictionaryDetailList", s.handleGetDictionaryDetailList)
|
||||||
r.GET("/sysDictionaryDetail/getDictionaryListByType", s.handleGetDictionaryListByType)
|
rg.Private.GET("/sysDictionaryDetail/getDictionaryTreeList", s.handleGetDictionaryTreeList)
|
||||||
r.GET("/sysDictionaryDetail/getDictionaryTreeListByType", s.handleGetDictionaryTreeListByType)
|
rg.Private.GET("/sysDictionaryDetail/getDictionaryTreeListByType", s.handleGetDictionaryTreeListByType)
|
||||||
|
rg.Private.GET("/sysDictionaryDetail/getDictionaryDetailsByParent", s.handleGetDictionaryDetailsByParent)
|
||||||
|
rg.Private.GET("/sysDictionaryDetail/getDictionaryPath", s.handleGetDictionaryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Handlers - Dictionary
|
// 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}})
|
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
|
// parseUint 解析uint
|
||||||
func parseUint(s string) (uint, error) {
|
func parseUint(s string) (uint, error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func (s *JwtBlacklistService) JoinBlacklist(ctx http.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *JwtBlacklistService) RegisterRoutes(srv *http.Server) {
|
func (s *JwtBlacklistService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// JWT黑名单(不需要操作记录)
|
||||||
r.POST("/jwt/jsonInBlacklist", s.JoinBlacklist)
|
rg.Private.POST("/jwt/jsonInBlacklist", s.JoinBlacklist)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -322,18 +322,19 @@ func toBaseMenuInfo(m *system.BaseMenu) *MenuInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *MenuService) RegisterRoutes(srv *http.Server) {
|
func (s *MenuService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 私有路由 + 操作记录(写操作)
|
||||||
|
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)
|
rg.Private.POST("/menu/getMenu", s.handleGetMenu)
|
||||||
r.POST("/menu/getMenuList", s.handleGetMenuList)
|
rg.Private.POST("/menu/getMenuList", s.handleGetMenuList)
|
||||||
r.POST("/menu/addBaseMenu", s.handleAddBaseMenu)
|
rg.Private.POST("/menu/getBaseMenuTree", s.handleGetBaseMenuTree)
|
||||||
r.POST("/menu/updateBaseMenu", s.handleUpdateBaseMenu)
|
rg.Private.POST("/menu/getMenuAuthority", s.handleGetMenuAuthority)
|
||||||
r.POST("/menu/deleteBaseMenu", s.handleDeleteBaseMenu)
|
rg.Private.POST("/menu/getBaseMenuById", s.handleGetBaseMenuById)
|
||||||
r.POST("/menu/getBaseMenuById", s.handleGetBaseMenuById)
|
|
||||||
r.POST("/menu/addMenuAuthority", s.handleAddMenuAuthority)
|
|
||||||
r.POST("/menu/getMenuAuthority", s.handleGetMenuAuthority)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Handlers
|
// HTTP Handlers
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
|
types "kra/internal/service/types/system"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/errors"
|
"github.com/go-kratos/kratos/v2/errors"
|
||||||
"github.com/go-kratos/kratos/v2/transport/http"
|
"github.com/go-kratos/kratos/v2/transport/http"
|
||||||
|
|
@ -21,18 +22,18 @@ func NewOperationRecordService(uc *system.OperationRecordUsecase) *OperationReco
|
||||||
|
|
||||||
// OperationRecordInfo 操作记录信息
|
// OperationRecordInfo 操作记录信息
|
||||||
type OperationRecordInfo struct {
|
type OperationRecordInfo struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Latency int64 `json:"latency"`
|
Latency int64 `json:"latency"`
|
||||||
Agent string `json:"agent"`
|
Agent string `json:"agent"`
|
||||||
ErrorMessage string `json:"error_message"`
|
ErrorMessage string `json:"error_message"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Resp string `json:"resp"`
|
Resp string `json:"resp"`
|
||||||
UserID uint `json:"user_id"`
|
UserID uint `json:"user_id"`
|
||||||
User *UserInfo `json:"user,omitempty"`
|
User *types.UserInfo `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOperationRecordListRequest 获取操作记录列表请求
|
// GetOperationRecordListRequest 获取操作记录列表请求
|
||||||
|
|
@ -64,12 +65,12 @@ type DeleteOperationRecordByIdsRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// RegisterRoutes 注册路由
|
||||||
func (s *OperationRecordService) RegisterRoutes(srv *http.Server) {
|
func (s *OperationRecordService) RegisterRoutes(rg *RouterGroup) {
|
||||||
r := srv.Route("/")
|
// 操作记录(不需要操作记录中间件,避免递归)
|
||||||
r.POST("/sysOperationRecord/getSysOperationRecordList", s.handleGetList)
|
rg.Private.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDelete)
|
||||||
r.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDelete)
|
rg.Private.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteByIds)
|
||||||
r.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteByIds)
|
rg.Private.GET("/sysOperationRecord/findSysOperationRecord", s.handleFind)
|
||||||
r.GET("/sysOperationRecord/findSysOperationRecord", s.handleFind)
|
rg.Private.GET("/sysOperationRecord/getSysOperationRecordList", s.handleGetList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OperationRecordService) handleGetList(ctx http.Context) error {
|
func (s *OperationRecordService) handleGetList(ctx http.Context) error {
|
||||||
|
|
@ -159,7 +160,8 @@ func toOperationRecordInfo(r *system.OperationRecord) *OperationRecordInfo {
|
||||||
UserID: r.UserID,
|
UserID: r.UserID,
|
||||||
}
|
}
|
||||||
if r.User != nil {
|
if r.User != nil {
|
||||||
info.User = toUserInfo(r.User)
|
userInfo := toTypesUserInfo(r.User)
|
||||||
|
info.User = &userInfo
|
||||||
}
|
}
|
||||||
return info
|
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
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"kra/internal/biz/system"
|
"kra/internal/biz/system"
|
||||||
"kra/internal/server/middleware"
|
"kra/internal/server/middleware"
|
||||||
|
types "kra/internal/service/types/system"
|
||||||
"kra/pkg/jwt"
|
"kra/pkg/jwt"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/errors"
|
"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 用户服务
|
// UserService 用户服务
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
uc *system.UserUsecase
|
uc *system.UserUsecase
|
||||||
|
|
@ -24,93 +30,43 @@ func NewUserService(uc *system.UserUsecase, jwtPkg *jwt.JWT) *UserService {
|
||||||
return &UserService{uc: uc, jwtPkg: jwtPkg}
|
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 用户登录
|
// 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)
|
user, err := s.uc.Login(ctx, req.Username, req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Unauthorized("LOGIN_FAILED", err.Error())
|
return errors.Unauthorized("LOGIN_FAILED", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Enable != 1 {
|
if user.Enable != 1 {
|
||||||
return nil, errors.Forbidden("USER_DISABLED", "用户被禁止登录")
|
return errors.Forbidden("USER_DISABLED", "用户被禁止登录")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 JWT token
|
|
||||||
claims := s.jwtPkg.CreateClaims(jwt.BaseClaims{
|
claims := s.jwtPkg.CreateClaims(jwt.BaseClaims{
|
||||||
UUID: user.UUID.String(),
|
UUID: user.UUID.String(),
|
||||||
ID: uint(user.ID),
|
ID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
NickName: user.NickName,
|
NickName: user.NickName,
|
||||||
AuthorityID: user.AuthorityId,
|
AuthorityID: user.AuthorityId,
|
||||||
})
|
})
|
||||||
token, err := s.jwtPkg.CreateToken(claims.BaseClaims)
|
token, err := s.jwtPkg.CreateToken(claims.BaseClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
return errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.LoginReply{
|
||||||
return &LoginResponse{
|
User: toTypesUserInfo(user),
|
||||||
User: toUserInfo(user),
|
|
||||||
Token: token,
|
Token: token,
|
||||||
ExpiresAt: claims.ExpiresAt.UnixMilli(),
|
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 用户注册
|
// 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{
|
user := &system.User{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
|
|
@ -121,66 +77,60 @@ func (s *UserService) Register(ctx context.Context, req *RegisterRequest) (*User
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Enable: req.Enable,
|
Enable: req.Enable,
|
||||||
}
|
}
|
||||||
|
|
||||||
created, err := s.uc.Register(ctx, user)
|
created, err := s.uc.Register(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequest("REGISTER_FAILED", err.Error())
|
return errors.BadRequest("REGISTER_FAILED", err.Error())
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.UserInfoReply{User: toTypesUserInfo(created)})
|
||||||
return toUserInfo(created), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangePasswordRequest 修改密码请求
|
|
||||||
type ChangePasswordRequest struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
NewPassword string `json:"newPassword"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangePassword 修改密码
|
// ChangePassword 修改密码
|
||||||
func (s *UserService) ChangePassword(ctx context.Context, userID uint, req *ChangePasswordRequest) error {
|
func (s *UserService) ChangePassword(ctx khttp.Context) error {
|
||||||
return s.uc.ChangePassword(ctx, userID, req.Password, req.NewPassword)
|
var req types.ChangePasswordRequest
|
||||||
}
|
if err := ctx.Bind(&req); err != nil {
|
||||||
|
return err
|
||||||
// ResetPasswordRequest 重置密码请求
|
}
|
||||||
type ResetPasswordRequest struct {
|
userID := middleware.GetUserID(ctx)
|
||||||
ID uint `json:"id"`
|
if userID == 0 {
|
||||||
Password string `json:"password"`
|
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 重置密码
|
// ResetPassword 重置密码
|
||||||
func (s *UserService) ResetPassword(ctx context.Context, req *ResetPasswordRequest) error {
|
func (s *UserService) ResetPassword(ctx khttp.Context) error {
|
||||||
return s.uc.ResetPassword(ctx, req.ID, req.Password)
|
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 获取用户信息
|
// GetUserInfo 获取用户信息
|
||||||
func (s *UserService) GetUserInfo(ctx context.Context, uuid string) (*UserInfo, error) {
|
func (s *UserService) GetUserInfo(ctx khttp.Context) error {
|
||||||
user, err := s.uc.GetUserInfo(ctx, uuid)
|
claims, ok := middleware.GetClaims(ctx)
|
||||||
if err != nil {
|
if !ok {
|
||||||
return nil, errors.NotFound("USER_NOT_FOUND", err.Error())
|
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||||
}
|
}
|
||||||
return toUserInfo(user), nil
|
user, err := s.uc.GetUserInfo(ctx, claims.UUID)
|
||||||
}
|
if err != nil {
|
||||||
|
return errors.NotFound("USER_NOT_FOUND", err.Error())
|
||||||
// GetUserListRequest 获取用户列表请求
|
}
|
||||||
type GetUserListRequest struct {
|
return ctx.Result(200, &types.GetUserInfoReply{UserInfo: toTypesUserInfo(user)})
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserList 获取用户列表
|
// 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)
|
filters := make(map[string]string)
|
||||||
if req.Username != "" {
|
if req.Username != "" {
|
||||||
filters["username"] = req.Username
|
filters["username"] = req.Username
|
||||||
|
|
@ -194,45 +144,23 @@ func (s *UserService) GetUserList(ctx context.Context, req *GetUserListRequest)
|
||||||
if req.Email != "" {
|
if req.Email != "" {
|
||||||
filters["email"] = req.Email
|
filters["email"] = req.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
users, total, err := s.uc.GetUserList(ctx, req.Page, req.PageSize, filters)
|
users, total, err := s.uc.GetUserList(ctx, req.Page, req.PageSize, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer("LIST_ERROR", err.Error())
|
return errors.InternalServer("LIST_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
list := make([]types.UserInfo, len(users))
|
||||||
list := make([]*UserInfo, len(users))
|
|
||||||
for i, u := range users {
|
for i, u := range users {
|
||||||
list[i] = toUserInfo(u)
|
list[i] = toTypesUserInfo(u)
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.GetUserListReply{List: list, Total: total, Page: req.Page, PageSize: req.PageSize})
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserInfo 设置用户信息
|
// SetUserInfo 设置用户信息
|
||||||
func (s *UserService) SetUserInfo(ctx context.Context, adminAuthorityID uint, req *SetUserInfoRequest) error {
|
func (s *UserService) SetUserInfo(ctx khttp.Context) error {
|
||||||
// 如果提供了AuthorityIds,先设置用户角色(与GVA保持一致)
|
var req types.SetUserInfoRequest
|
||||||
if len(req.AuthorityIds) > 0 {
|
if err := ctx.Bind(&req); err != nil {
|
||||||
if err := s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &system.User{
|
user := &system.User{
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
NickName: req.NickName,
|
NickName: req.NickName,
|
||||||
|
|
@ -241,360 +169,177 @@ func (s *UserService) SetUserInfo(ctx context.Context, adminAuthorityID uint, re
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Enable: req.Enable,
|
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 设置自己信息请求(用户用)
|
// SetSelfInfo 设置自己信息
|
||||||
type SetSelfInfoRequest struct {
|
func (s *UserService) SetSelfInfo(ctx khttp.Context) error {
|
||||||
NickName string `json:"nickName"`
|
var req types.SetSelfInfoRequest
|
||||||
HeaderImg string `json:"headerImg"`
|
if err := ctx.Bind(&req); err != nil {
|
||||||
Phone string `json:"phone"`
|
return err
|
||||||
Email string `json:"email"`
|
}
|
||||||
SideMode string `json:"sideMode"`
|
userID := middleware.GetUserID(ctx)
|
||||||
BaseColor string `json:"baseColor"`
|
if userID == 0 {
|
||||||
}
|
return errors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||||
|
}
|
||||||
// SetSelfInfo 设置自己的信息
|
|
||||||
func (s *UserService) SetSelfInfo(ctx context.Context, userID uint, req *SetSelfInfoRequest) error {
|
|
||||||
user := &system.User{
|
user := &system.User{
|
||||||
ID: userID,
|
ID: userID,
|
||||||
NickName: req.NickName,
|
NickName: req.NickName,
|
||||||
HeaderImg: req.HeaderImg,
|
HeaderImg: req.HeaderImg,
|
||||||
Phone: req.Phone,
|
Phone: req.Phone,
|
||||||
Email: req.Email,
|
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 设置自己的配置
|
// SetSelfSetting 设置自己配置
|
||||||
func (s *UserService) SetSelfSetting(ctx context.Context, userID uint, setting json.RawMessage) error {
|
func (s *UserService) SetSelfSetting(ctx khttp.Context) error {
|
||||||
return s.uc.SetSelfSetting(ctx, userID, setting)
|
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 删除用户
|
// DeleteUser 删除用户
|
||||||
func (s *UserService) DeleteUser(ctx context.Context, id uint) error {
|
func (s *UserService) DeleteUser(ctx khttp.Context) error {
|
||||||
return s.uc.DeleteUser(ctx, id)
|
var req types.DeleteUserRequest
|
||||||
}
|
if err := ctx.Bind(&req); err != nil {
|
||||||
|
return err
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
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保持一致)
|
// SetUserAuthority 设置用户角色
|
||||||
claims := s.jwtPkg.CreateClaims(jwt.BaseClaims{
|
func (s *UserService) SetUserAuthority(ctx khttp.Context) error {
|
||||||
UUID: uuid,
|
var req types.SetUserAuthorityRequest
|
||||||
ID: userID,
|
if err := ctx.Bind(&req); err != nil {
|
||||||
Username: username,
|
return err
|
||||||
NickName: nickName,
|
}
|
||||||
AuthorityID: req.AuthorityId,
|
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)
|
token, err := s.jwtPkg.CreateToken(claims.BaseClaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
return errors.InternalServer("TOKEN_ERROR", "生成token失败")
|
||||||
}
|
}
|
||||||
|
return ctx.Result(200, &types.SetUserAuthorityReply{Token: token, ExpiresAt: claims.ExpiresAt.UnixMilli()})
|
||||||
return &SetUserAuthorityResponse{
|
|
||||||
Token: token,
|
|
||||||
ExpiresAt: claims.ExpiresAt.UnixMilli(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUserAuthoritiesRequest 设置用户多角色请求
|
|
||||||
type SetUserAuthoritiesRequest struct {
|
|
||||||
ID uint `json:"id"`
|
|
||||||
AuthorityIds []uint `json:"authorityIds"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserAuthorities 设置用户多角色
|
// SetUserAuthorities 设置用户多角色
|
||||||
func (s *UserService) SetUserAuthorities(ctx context.Context, adminAuthorityID uint, req *SetUserAuthoritiesRequest) error {
|
func (s *UserService) SetUserAuthorities(ctx khttp.Context) error {
|
||||||
return s.uc.SetUserAuthorities(ctx, adminAuthorityID, req.ID, req.AuthorityIds)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换函数
|
// toTypesUserInfo 转换用户信息为types格式
|
||||||
func toUserInfo(u *system.User) *UserInfo {
|
func toTypesUserInfo(user *system.User) types.UserInfo {
|
||||||
info := &UserInfo{
|
if user == nil {
|
||||||
ID: u.ID,
|
return types.UserInfo{}
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
info := types.UserInfo{
|
||||||
if u.Authority != nil {
|
ID: user.ID,
|
||||||
info.Authority = &AuthorityInfo{
|
UUID: user.UUID.String(),
|
||||||
AuthorityId: u.Authority.AuthorityId,
|
Username: user.Username,
|
||||||
AuthorityName: u.Authority.AuthorityName,
|
NickName: user.NickName,
|
||||||
ParentId: u.Authority.ParentId,
|
SideMode: user.SideMode,
|
||||||
DefaultRouter: u.Authority.DefaultRouter,
|
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
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRoutes 注册路由
|
// toTypesAuthorityInfo 转换角色信息为types格式
|
||||||
func (s *UserService) RegisterRoutes(srv *http.Server) {
|
func toTypesAuthorityInfo(auth *system.Authority) types.AuthorityInfo {
|
||||||
r := srv.Route("/")
|
if auth == nil {
|
||||||
|
return types.AuthorityInfo{}
|
||||||
// 公开接口
|
}
|
||||||
r.POST("/base/login", s.handleLogin)
|
return types.AuthorityInfo{
|
||||||
|
AuthorityId: auth.AuthorityId,
|
||||||
// 需要认证的接口
|
AuthorityName: auth.AuthorityName,
|
||||||
r.POST("/user/register", s.handleRegister)
|
ParentId: auth.ParentId,
|
||||||
r.POST("/user/changePassword", s.handleChangePassword)
|
DefaultRouter: auth.DefaultRouter,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Handlers
|
// RegisterRoutes 注册用户路由
|
||||||
func (s *UserService) handleLogin(ctx http.Context) error {
|
func (s *UserService) RegisterRoutes(rg *RouterGroup) {
|
||||||
var req LoginRequest
|
// 公开路由(无需认证)
|
||||||
if err := ctx.Bind(&req); err != nil {
|
rg.Public.POST("/base/login", s.Login)
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UserService) handleRegister(ctx http.Context) error {
|
// 私有路由 + 操作记录(写操作)
|
||||||
var req RegisterRequest
|
rg.PrivateWithRecord.POST("/user/admin_register", s.Register)
|
||||||
if err := ctx.Bind(&req); err != nil {
|
rg.PrivateWithRecord.POST("/user/changePassword", s.ChangePassword)
|
||||||
return err
|
rg.PrivateWithRecord.POST("/user/setUserAuthority", s.SetUserAuthority)
|
||||||
}
|
rg.PrivateWithRecord.DELETE("/user/deleteUser", s.DeleteUser)
|
||||||
resp, err := s.Register(ctx, &req)
|
rg.PrivateWithRecord.PUT("/user/setUserInfo", s.SetUserInfo)
|
||||||
if err != nil {
|
rg.PrivateWithRecord.PUT("/user/setSelfInfo", s.SetSelfInfo)
|
||||||
return err
|
rg.PrivateWithRecord.POST("/user/setUserAuthorities", s.SetUserAuthorities)
|
||||||
}
|
rg.PrivateWithRecord.POST("/user/resetPassword", s.ResetPassword)
|
||||||
return ctx.Result(200, map[string]any{
|
rg.PrivateWithRecord.PUT("/user/setSelfSetting", s.SetSelfSetting)
|
||||||
"code": 0,
|
|
||||||
"msg": "注册成功",
|
|
||||||
"data": resp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UserService) handleChangePassword(ctx http.Context) error {
|
// 私有路由(读操作,不记录)
|
||||||
var req ChangePasswordRequest
|
rg.Private.POST("/user/getUserList", s.GetUserList)
|
||||||
if err := ctx.Bind(&req); err != nil {
|
rg.Private.GET("/user/getUserInfo", s.GetUserInfo)
|
||||||
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": "修改成功",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
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: {
|
mock: {
|
||||||
include: ["mock/**/*", "src/pages/**/_mock.ts"],
|
include: ["mock/**/*", "src/pages/**/_mock.ts"],
|
||||||
},
|
},
|
||||||
utoopack: {},
|
|
||||||
requestRecord: {},
|
requestRecord: {},
|
||||||
exportStatic: {},
|
exportStatic: {},
|
||||||
define: {
|
define: {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ export default {
|
||||||
* @doc https://github.com/chimurai/http-proxy-middleware
|
* @doc https://github.com/chimurai/http-proxy-middleware
|
||||||
*/
|
*/
|
||||||
dev: {
|
dev: {
|
||||||
"/v1/": {
|
"/api/": {
|
||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
pathRewrite: { "^/api": "" },
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,18 @@ export default [
|
||||||
icon: 'fileSearch',
|
icon: 'fileSearch',
|
||||||
component: './system/operation',
|
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,
|
Question,
|
||||||
SelectLang,
|
SelectLang,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { createAdminService } from "@/services/index";
|
import { getUserInfo } from "@/services/system/user";
|
||||||
import { Admin } from "@/services/kratos/admin/v1/index";
|
|
||||||
import { LinkOutlined } from "@ant-design/icons";
|
import { LinkOutlined } from "@ant-design/icons";
|
||||||
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
|
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
|
||||||
import { SettingDrawer } 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 isDevOrTest = isDev || process.env.CI;
|
||||||
const loginPath = "/user/login";
|
const loginPath = "/user/login";
|
||||||
|
|
||||||
const adminService = createAdminService();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://umijs.org/docs/api/runtime-config#getinitialstate
|
* @see https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||||
* */
|
* */
|
||||||
export async function getInitialState(): Promise<{
|
export async function getInitialState(): Promise<{
|
||||||
settings?: Partial<LayoutSettings>;
|
settings?: Partial<LayoutSettings>;
|
||||||
currentUser?: API.CurrentUser;
|
currentUser?: API.UserInfo;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
fetchUserInfo?: () => Promise<Admin | undefined>;
|
fetchUserInfo?: () => Promise<API.UserInfo | undefined>;
|
||||||
}> {
|
}> {
|
||||||
const fetchUserInfo = async () => {
|
const fetchUserInfo = async () => {
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
history.push(loginPath);
|
history.push(loginPath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { createAdminService } from "@/services/index";
|
|
||||||
import {
|
import {
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
|
|
@ -12,8 +11,6 @@ import React from "react";
|
||||||
import { flushSync } from "react-dom";
|
import { flushSync } from "react-dom";
|
||||||
import HeaderDropdown from "../HeaderDropdown";
|
import HeaderDropdown from "../HeaderDropdown";
|
||||||
|
|
||||||
const adminService = createAdminService();
|
|
||||||
|
|
||||||
export type GlobalHeaderRightProps = {
|
export type GlobalHeaderRightProps = {
|
||||||
menu?: boolean;
|
menu?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -22,7 +19,7 @@ export type GlobalHeaderRightProps = {
|
||||||
export const AvatarName = () => {
|
export const AvatarName = () => {
|
||||||
const { initialState } = useModel("@@initialState");
|
const { initialState } = useModel("@@initialState");
|
||||||
const { currentUser } = initialState || {};
|
const { currentUser } = initialState || {};
|
||||||
return <span className="anticon">{currentUser?.name}</span>;
|
return <span className="anticon">{currentUser?.nickName || currentUser?.userName}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = createStyles(({ token }) => {
|
const useStyles = createStyles(({ token }) => {
|
||||||
|
|
@ -51,7 +48,9 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
* 退出登录,并且将当前的 url 保存
|
* 退出登录,并且将当前的 url 保存
|
||||||
*/
|
*/
|
||||||
const loginOut = async () => {
|
const loginOut = async () => {
|
||||||
await adminService.Logout({});
|
// 清除本地存储的token和用户信息
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('userInfo');
|
||||||
const { search, pathname } = window.location;
|
const { search, pathname } = window.location;
|
||||||
const urlParams = new URL(window.location.href).searchParams;
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
|
|
@ -80,6 +79,10 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
loginOut();
|
loginOut();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key === "center") {
|
||||||
|
history.push('/person');
|
||||||
|
return;
|
||||||
|
}
|
||||||
history.push(`/account/${key}`);
|
history.push(`/account/${key}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -101,7 +104,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
|
|
||||||
const { currentUser } = initialState;
|
const { currentUser } = initialState;
|
||||||
|
|
||||||
if (!currentUser || !currentUser.name) {
|
if (!currentUser || (!currentUser.nickName && !currentUser.userName)) {
|
||||||
return loading;
|
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 { PageContainer } from '@ant-design/pro-components';
|
||||||
import { Card, Col, Row, Statistic } from 'antd';
|
import { Card, Col, Row, Statistic, Tag, List, Typography, Progress, Space, Tooltip } from 'antd';
|
||||||
import { UserOutlined, TeamOutlined, ApiOutlined, MenuOutlined } from '@ant-design/icons';
|
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 = () => {
|
const Dashboard: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer
|
||||||
<Row gutter={16}>
|
header={{ title: '' }}
|
||||||
<Col span={6}>
|
className={styles.dashboard}
|
||||||
<Card>
|
>
|
||||||
<Statistic title="用户总数" value={128} prefix={<UserOutlined />} />
|
{/* 统计卡片行 */}
|
||||||
|
<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>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
{/* 右侧快捷功能 */}
|
||||||
<Statistic title="角色数量" value={8} prefix={<TeamOutlined />} />
|
<Col xs={24} lg={6}>
|
||||||
</Card>
|
<Card title="快捷功能" bordered={false} className={styles.quickCard}>
|
||||||
</Col>
|
<Row gutter={[8, 16]}>
|
||||||
<Col span={6}>
|
{shortcuts.map((item, index) => (
|
||||||
<Card>
|
<Col span={8} key={index}>
|
||||||
<Statistic title="API数量" value={256} prefix={<ApiOutlined />} />
|
<div
|
||||||
</Card>
|
className={styles.shortcutItem}
|
||||||
</Col>
|
onClick={() => history.push(item.path)}
|
||||||
<Col span={6}>
|
>
|
||||||
<Card>
|
<div className={styles.shortcutIcon}>{item.icon}</div>
|
||||||
<Statistic title="菜单数量" value={32} prefix={<MenuOutlined />} />
|
<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>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Card style={{ marginTop: 16 }} title="欢迎使用 Kratos Admin">
|
|
||||||
<p>这是一个基于 Kratos 框架的后台管理系统,前端使用 React + Ant Design Pro。</p>
|
|
||||||
<p>功能与 GVA (gin-vue-admin) 保持一致。</p>
|
|
||||||
</Card>
|
|
||||||
</PageContainer>
|
</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 { LockOutlined, UserOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
|
||||||
import { LoginForm, ProFormText } from '@ant-design/pro-components';
|
|
||||||
import { history, useModel } from '@umijs/max';
|
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 { useState, useEffect } from 'react';
|
||||||
import { login, captcha } from '@/services/system/user';
|
import { login, captcha } from '@/services/system/user';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const { setInitialState } = useModel('@@initialState');
|
const { setInitialState } = useModel('@@initialState');
|
||||||
|
const [form] = Form.useForm();
|
||||||
const [captchaInfo, setCaptchaInfo] = useState<{
|
const [captchaInfo, setCaptchaInfo] = useState<{
|
||||||
captchaId: string;
|
captchaId: string;
|
||||||
picPath: string;
|
picPath: string;
|
||||||
openCaptcha: boolean;
|
openCaptcha: boolean;
|
||||||
}>({ captchaId: '', picPath: '', openCaptcha: false });
|
captchaLength: number;
|
||||||
|
}>({ captchaId: '', picPath: '', openCaptcha: false, captchaLength: 6 });
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
|
|
@ -27,6 +29,7 @@ const Login: React.FC = () => {
|
||||||
captchaId: res.data.captchaId,
|
captchaId: res.data.captchaId,
|
||||||
picPath: res.data.picPath,
|
picPath: res.data.picPath,
|
||||||
openCaptcha: res.data.openCaptcha,
|
openCaptcha: res.data.openCaptcha,
|
||||||
|
captchaLength: res.data.captchaLength || 6,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -48,15 +51,12 @@ const Login: React.FC = () => {
|
||||||
});
|
});
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
// 存储token
|
|
||||||
localStorage.setItem('token', res.data.token);
|
localStorage.setItem('token', res.data.token);
|
||||||
localStorage.setItem('userInfo', JSON.stringify(res.data.user));
|
localStorage.setItem('userInfo', JSON.stringify(res.data.user));
|
||||||
// 更新全局状态
|
|
||||||
await setInitialState((s) => ({
|
await setInitialState((s) => ({
|
||||||
...s,
|
...s,
|
||||||
currentUser: res.data?.user,
|
currentUser: res.data?.user,
|
||||||
}));
|
}));
|
||||||
// 跳转到首页
|
|
||||||
const urlParams = new URL(window.location.href).searchParams;
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
history.push(urlParams.get('redirect') || '/');
|
history.push(urlParams.get('redirect') || '/');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -71,57 +71,122 @@ const Login: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div className={styles.container}>
|
||||||
display: 'flex',
|
<div className={styles.content}>
|
||||||
justifyContent: 'center',
|
{/* 左侧登录表单区域 */}
|
||||||
alignItems: 'center',
|
<div className={styles.leftSection}>
|
||||||
height: '100vh',
|
{/* 斜切装饰块 */}
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
<div className={styles.oblique} />
|
||||||
}}>
|
|
||||||
<Spin spinning={loading}>
|
<div className={styles.formWrapper}>
|
||||||
<LoginForm
|
<Spin spinning={loading}>
|
||||||
title="Kratos Admin"
|
{/* Logo 和标题 */}
|
||||||
subTitle="基于 Kratos 的后台管理系统"
|
<div className={styles.header}>
|
||||||
onFinish={handleSubmit}
|
<div className={styles.logo}>
|
||||||
initialValues={{ username: 'admin' }}
|
<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"
|
||||||
<ProFormText
|
stroke="#194bfb" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
name="username"
|
</svg>
|
||||||
fieldProps={{ size: 'large', prefix: <UserOutlined /> }}
|
</div>
|
||||||
placeholder="请输入用户名"
|
<h1 className={styles.title}>Kratos Admin</h1>
|
||||||
rules={[
|
<p className={styles.subtitle}>基于 Go Kratos 和 React 的后台管理系统</p>
|
||||||
{ required: true, message: '请输入用户名' },
|
</div>
|
||||||
{ min: 5, message: '用户名至少5个字符' },
|
|
||||||
]}
|
{/* 登录表单 */}
|
||||||
/>
|
<Form
|
||||||
<ProFormText.Password
|
form={form}
|
||||||
name="password"
|
onFinish={handleSubmit}
|
||||||
fieldProps={{ size: 'large', prefix: <LockOutlined /> }}
|
initialValues={{ username: 'admin' }}
|
||||||
placeholder="请输入密码"
|
size="large"
|
||||||
rules={[
|
>
|
||||||
{ required: true, message: '请输入密码' },
|
<Form.Item
|
||||||
{ min: 6, message: '密码至少6个字符' },
|
name="username"
|
||||||
]}
|
rules={[
|
||||||
/>
|
{ required: true, message: '请输入用户名' },
|
||||||
{captchaInfo.openCaptcha && (
|
{ min: 5, message: '用户名至少5个字符' },
|
||||||
<div style={{ display: 'flex', gap: 8, marginBottom: 24 }}>
|
]}
|
||||||
<ProFormText
|
>
|
||||||
name="captcha"
|
<Input
|
||||||
fieldProps={{ size: 'large', prefix: <SafetyCertificateOutlined /> }}
|
prefix={<UserOutlined className={styles.inputIcon} />}
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入用户名"
|
||||||
rules={[{ required: true, message: '请输入验证码' }]}
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
<Image
|
</Form.Item>
|
||||||
src={captchaInfo.picPath}
|
|
||||||
alt="验证码"
|
<Form.Item
|
||||||
preview={false}
|
name="password"
|
||||||
style={{ height: 40, cursor: 'pointer' }}
|
rules={[
|
||||||
onClick={getCaptcha}
|
{ 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>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</LoginForm>
|
</div>
|
||||||
</Spin>
|
</div>
|
||||||
|
|
||||||
|
{/* 底部信息 */}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<span>Kratos Admin © 2024 基于 Go Kratos 框架</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,43 @@
|
||||||
import type { RequestOptions } from "@@/plugin-request/request";
|
import type { RequestOptions } from "@@/plugin-request/request";
|
||||||
import type { RequestConfig } from "@umijs/max";
|
import type { RequestConfig } from "@umijs/max";
|
||||||
import { message as toast } from "antd";
|
import { message as toast } from "antd";
|
||||||
|
import { history } from "@umijs/max";
|
||||||
|
|
||||||
// Define the structure of the expected response.
|
// Define the structure of the expected response.
|
||||||
interface ResponseStructure {
|
interface ResponseStructure {
|
||||||
code: number;
|
code: number;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
msg?: string;
|
||||||
metadata?: Map<string, number>;
|
metadata?: Map<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginPath = "/user/login";
|
||||||
|
|
||||||
// Request configuration with error handling and interceptors.
|
// Request configuration with error handling and interceptors.
|
||||||
export const errorConfig: RequestConfig = {
|
export const errorConfig: RequestConfig = {
|
||||||
errorConfig: {
|
errorConfig: {
|
||||||
errorThrower: (res) => {
|
errorThrower: (res) => {
|
||||||
const { code, reason, message } = res as unknown as ResponseStructure;
|
const { code, reason, message, msg } = res as unknown as ResponseStructure;
|
||||||
if (code != 200) {
|
if (code !== 0 && code !== 200) {
|
||||||
const error: any = new Error(message);
|
const error: any = new Error(message || msg);
|
||||||
error.info = { reason, message };
|
error.info = { reason, message: message || msg };
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errorHandler: (error: any, opts: any) => {
|
errorHandler: (error: any, opts: any) => {
|
||||||
if (opts?.skipErrorHandler) throw error;
|
if (opts?.skipErrorHandler) throw error;
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('userInfo');
|
||||||
|
history.push(loginPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
const errorInfo: ResponseStructure | undefined = error.response?.data;
|
const errorInfo: ResponseStructure | undefined = error.response?.data;
|
||||||
if (errorInfo) {
|
if (errorInfo) {
|
||||||
const { message, reason } = errorInfo;
|
const { message, msg, reason } = errorInfo;
|
||||||
toast.error(reason + ": " + message);
|
toast.error((reason ? reason + ": " : "") + (message || msg || "请求失败"));
|
||||||
}
|
}
|
||||||
} else if (error.response) {
|
} else if (error.response) {
|
||||||
toast.error(`Response status: ${error.response.status}`);
|
toast.error(`Response status: ${error.response.status}`);
|
||||||
|
|
@ -40,12 +50,20 @@ export const errorConfig: RequestConfig = {
|
||||||
},
|
},
|
||||||
requestInterceptors: [
|
requestInterceptors: [
|
||||||
(config: RequestOptions) => {
|
(config: RequestOptions) => {
|
||||||
|
// 添加token到请求头
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
config.headers = {
|
||||||
|
...config.headers,
|
||||||
|
'x-token': token,
|
||||||
|
};
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
responseInterceptors: [
|
responseInterceptors: [
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response.status != 200) {
|
if (response.status !== 200) {
|
||||||
toast.error(`Request error: ${response.status}`);
|
toast.error(`Request error: ${response.status}`);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,5 @@ export * from './dictionary';
|
||||||
export * from './dictionaryDetail';
|
export * from './dictionaryDetail';
|
||||||
export * from './operationRecord';
|
export * from './operationRecord';
|
||||||
export * from './casbin';
|
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