feat: add redis

This commit is contained in:
2025-08-29 21:42:29 +08:00
parent af68d94efa
commit dc60a0a4bd
10 changed files with 875 additions and 52 deletions

View File

@ -1,10 +1,14 @@
RUST_LOG=info,udmin=debug RUST_LOG=info,udmin=debug
APP_ENV=development APP_ENV=development
APP_HOST=0.0.0.0 APP_HOST=0.0.0.0
APP_PORT=9898 APP_PORT=9891
DB_URL=mysql://root:123456@127.0.0.1:3306/udmin DB_URL=mysql://root:123456@127.0.0.1:3306/udmin
JWT_SECRET=dev_secret_change_me JWT_SECRET=dev_secret_change_me
JWT_ISS=udmin JWT_ISS=udmin
JWT_ACCESS_EXP_SECS=1800 JWT_ACCESS_EXP_SECS=1800
JWT_REFRESH_EXP_SECS=1209600 JWT_REFRESH_EXP_SECS=1209600
CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost:5174,http://localhost:5175 CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost:5174,http://localhost:5175
# Redis配置
REDIS_URL=redis://:123456@127.0.0.1:6379/9
REDIS_TOKEN_VALIDATION=true

View File

@ -1,10 +1,22 @@
RUST_LOG=info,udmin=debug # 数据库配置
APP_ENV=development DATABASE_URL=sqlite://udmin.db?mode=rwc
APP_HOST=0.0.0.0
APP_PORT=9898 # JWT配置
DB_URL=mysql://root:123456@127.0.0.1:3306/udmin JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_SECRET=please_change_me
JWT_ISS=udmin JWT_ISS=udmin
JWT_ACCESS_EXP_SECS=1800 JWT_ACCESS_EXP_SECS=1800 # 30分钟
JWT_REFRESH_EXP_SECS=1209600 JWT_REFRESH_EXP_SECS=1209600 # 14天
# Redis配置
REDIS_URL=redis://:123456@127.0.0.1:6379/9
REDIS_TOKEN_VALIDATION=true # 是否启用Redis token验证
# 服务器配置
APP_HOST=0.0.0.0
APP_PORT=8080
# CORS配置
CORS_ALLOW_ORIGINS=http://localhost:5173 CORS_ALLOW_ORIGINS=http://localhost:5173
# 日志级别
RUST_LOG=info

573
backend/Cargo.lock generated
View File

@ -35,6 +35,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"const-random",
"getrandom 0.3.3",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@ -132,6 +134,12 @@ version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]] [[package]]
name = "argon2" name = "argon2"
version = "0.5.3" version = "0.5.3"
@ -220,10 +228,10 @@ dependencies = [
"axum-core 0.4.5", "axum-core 0.4.5",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper", "hyper 1.7.0",
"hyper-util", "hyper-util",
"itoa", "itoa",
"matchit 0.7.3", "matchit 0.7.3",
@ -236,7 +244,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper 1.0.2",
"tokio", "tokio",
"tower", "tower",
"tower-layer", "tower-layer",
@ -254,10 +262,10 @@ dependencies = [
"bytes", "bytes",
"form_urlencoded", "form_urlencoded",
"futures-util", "futures-util",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper", "hyper 1.7.0",
"hyper-util", "hyper-util",
"itoa", "itoa",
"matchit 0.8.4", "matchit 0.8.4",
@ -270,7 +278,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper 1.0.2",
"tokio", "tokio",
"tower", "tower",
"tower-layer", "tower-layer",
@ -287,13 +295,13 @@ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion", "rustversion",
"sync_wrapper", "sync_wrapper 1.0.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
@ -307,18 +315,27 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion", "rustversion",
"sync_wrapper", "sync_wrapper 1.0.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
] ]
[[package]]
name = "backon"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d"
dependencies = [
"fastrand",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.75" version = "0.3.75"
@ -366,6 +383,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.2" version = "2.9.2"
@ -550,6 +573,20 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"futures-core",
"memchr",
"pin-project-lite",
"tokio",
"tokio-util",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@ -613,6 +650,16 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@ -829,6 +876,18 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.2" version = "1.1.2"
@ -885,6 +944,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -935,6 +995,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@ -953,8 +1024,10 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
@ -1010,6 +1083,25 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "h2"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.10.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -1103,6 +1195,17 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.3.1" version = "1.3.1"
@ -1114,6 +1217,17 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.1" version = "1.0.1"
@ -1121,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http 1.3.1",
] ]
[[package]] [[package]]
@ -1132,8 +1246,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"pin-project-lite", "pin-project-lite",
] ]
@ -1149,6 +1263,30 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.7.0" version = "1.7.0"
@ -1159,8 +1297,8 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@ -1170,6 +1308,20 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.32",
"rustls 0.21.12",
"tokio",
"tokio-rustls",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.16" version = "0.1.16"
@ -1178,9 +1330,9 @@ checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"hyper", "hyper 1.7.0",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tower-service", "tower-service",
@ -1356,23 +1508,47 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.9" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.2",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.1" version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@ -1442,7 +1618,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.2",
"libc", "libc",
"redox_syscall", "redox_syscall",
] ]
@ -1666,6 +1842,9 @@ name = "once_cell"
version = "1.21.3" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "once_cell_polyfill" name = "once_cell_polyfill"
@ -1837,6 +2016,16 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "petgraph"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.10.0",
]
[[package]] [[package]]
name = "pgvector" name = "pgvector"
version = "0.4.1" version = "0.4.1"
@ -1885,6 +2074,12 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.2" version = "0.1.2"
@ -2057,13 +2252,39 @@ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
] ]
[[package]]
name = "redis"
version = "0.27.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc"
dependencies = [
"arc-swap",
"async-trait",
"backon",
"bytes",
"combine",
"futures",
"futures-util",
"itertools",
"itoa",
"num-bigint",
"percent-encoding",
"pin-project-lite",
"ryu",
"sha1_smol",
"socket2 0.5.10",
"tokio",
"tokio-util",
"url",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.17" version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.2",
] ]
[[package]] [[package]]
@ -2139,6 +2360,77 @@ dependencies = [
"bytecheck", "bytecheck",
] ]
[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.32",
"hyper-rustls",
"ipnet",
"js-sys",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.21.12",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"system-configuration",
"tokio",
"tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 0.25.4",
"winreg",
]
[[package]]
name = "rhai"
version = "1.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249"
dependencies = [
"ahash 0.8.12",
"bitflags 2.9.2",
"instant",
"num-traits",
"once_cell",
"rhai_codegen",
"serde",
"serde_json",
"smallvec",
"smartstring",
"thin-vec",
]
[[package]]
name = "rhai_codegen"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@ -2189,7 +2481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bitflags", "bitflags 2.9.2",
"serde", "serde",
"serde_derive", "serde_derive",
] ]
@ -2280,6 +2572,18 @@ version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustls"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"rustls-webpki 0.101.7",
"sct",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.31" version = "0.23.31"
@ -2289,11 +2593,20 @@ dependencies = [
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki 0.103.4",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.12.0" version = "1.12.0"
@ -2303,6 +2616,16 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.4" version = "0.103.4"
@ -2365,6 +2688,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "sea-bae" name = "sea-bae"
version = "0.2.1" version = "0.2.1"
@ -2624,6 +2957,19 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.10.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -2635,6 +2981,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.9"
@ -2713,6 +3065,28 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "smartstring"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [
"autocfg",
"serde",
"static_assertions",
"version_check",
]
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.0" version = "0.6.0"
@ -2781,7 +3155,7 @@ dependencies = [
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"rust_decimal", "rust_decimal",
"rustls", "rustls 0.23.31",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -2843,7 +3217,7 @@ dependencies = [
"atoi", "atoi",
"base64 0.22.1", "base64 0.22.1",
"bigdecimal", "bigdecimal",
"bitflags", "bitflags 2.9.2",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono", "chrono",
@ -2890,7 +3264,7 @@ dependencies = [
"atoi", "atoi",
"base64 0.22.1", "base64 0.22.1",
"bigdecimal", "bigdecimal",
"bitflags", "bitflags 2.9.2",
"byteorder", "byteorder",
"chrono", "chrono",
"crc", "crc",
@ -3014,6 +3388,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "sync_wrapper" name = "sync_wrapper"
version = "1.0.2" version = "1.0.2"
@ -3031,12 +3411,42 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "tap" name = "tap"
version = "1.0.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "thin-vec"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@ -3166,7 +3576,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.6.0",
"tokio-macros", "tokio-macros",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -3182,6 +3592,16 @@ dependencies = [
"syn 2.0.106", "syn 2.0.106",
] ]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
"tokio",
]
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.17" version = "0.1.17"
@ -3193,6 +3613,19 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "0.8.23"
@ -3243,7 +3676,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"pin-project-lite", "pin-project-lite",
"sync_wrapper", "sync_wrapper 1.0.2",
"tokio", "tokio",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@ -3256,10 +3689,10 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.2",
"bytes", "bytes",
"http", "http 1.3.1",
"http-body", "http-body 1.0.1",
"pin-project-lite", "pin-project-lite",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@ -3340,6 +3773,12 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.18.0" version = "1.18.0"
@ -3364,15 +3803,21 @@ dependencies = [
"chrono", "chrono",
"config", "config",
"dotenvy", "dotenvy",
"hyper", "hyper 1.7.0",
"jsonwebtoken", "jsonwebtoken",
"migration", "migration",
"once_cell", "once_cell",
"petgraph",
"rand", "rand",
"redis",
"regex",
"reqwest",
"rhai",
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"serde_yaml",
"sha2", "sha2",
"thiserror 1.0.69", "thiserror 1.0.69",
"tokio", "tokio",
@ -3424,6 +3869,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@ -3558,6 +4009,15 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
@ -3605,6 +4065,19 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.100" version = "0.2.100"
@ -3637,6 +4110,22 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.11" version = "0.26.11"
@ -3986,13 +4475,23 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "wit-bindgen-rt" name = "wit-bindgen-rt"
version = "0.39.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.2",
] ]
[[package]] [[package]]

View File

@ -2,6 +2,7 @@
name = "udmin" name = "udmin"
version = "0.1.0" version = "0.1.0"
edition = "2024" # ✅ 升级到最新 Rust Edition edition = "2024" # ✅ 升级到最新 Rust Edition
default-run = "udmin"
[dependencies] [dependencies]
axum = "0.8.4" axum = "0.8.4"
@ -30,6 +31,14 @@ utoipa-swagger-ui = { version = "6.0.0", features = ["axum"] }
sha2 = "0.10" sha2 = "0.10"
rand = "0.8" rand = "0.8"
async-trait = "0.1" async-trait = "0.1"
redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
# 流程管理相关依赖
petgraph = "0.6"
rhai = { version = "1.17", features = ["serde", "metadata", "internals"] }
serde_yaml = "0.9"
regex = "1.10"
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
[dependencies.migration] [dependencies.migration]
path = "migration" path = "migration"

View File

@ -0,0 +1,78 @@
# Redis集成测试
这个文件包含了Redis集成的测试说明。
## 环境配置
1. 复制环境配置文件:
```bash
cp .env.example .env
```
2. 根据需要修改 `.env` 文件中的Redis配置
```bash
REDIS_URL=redis://:123456@127.0.0.1:6379/9
```
## 启动Redis服务
确保Redis服务器正在运行
```bash
# 使用Docker运行Redis
docker run -d -p 6379:6379 --name redis-udmin redis:7-alpine --requirepass 123456
# 或者使用本地Redis服务
redis-server --requirepass 123456
```
## 测试Redis连接
启动服务器:
```bash
cargo run
```
查看日志中是否有Redis连接成功的信息
```
[INFO] Connecting to Redis at: redis://:***@127.0.0.1:6379/9
[INFO] Redis connection established successfully
```
## 测试Token存储
1. 登录获取token
```bash
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin@123"}'
```
2. 检查Redis中的token
```bash
# 连接到Redis CLI
redis-cli -a 123456 -n 9
# 查看所有token键
KEYS token:*
# 查看特定用户的token
GET token:access:user:1
GET token:refresh:user:1
```
## 功能验证
Redis集成后的新功能
1. **Token存储**所有JWT token都存储在Redis中
2. **Token验证**每次API调用都会验证Redis中的token
3. **Token撤销**logout时会从Redis中删除token
4. **性能提升**:减少数据库查询,提升认证性能
5. **单点登录**:防止多重登录(可配置)
## 环境变量说明
- `REDIS_URL`: Redis连接字符串
- `REDIS_TOKEN_VALIDATION`: 是否启用Redis token验证默认true
- `JWT_ACCESS_EXP_SECS`: 访问token过期时间
- `JWT_REFRESH_EXP_SECS`: 刷新token过期时间

View File

@ -1,4 +1,5 @@
mod db; mod db;
mod redis;
mod response; mod response;
mod error; mod error;
pub mod middlewares; pub mod middlewares;
@ -6,6 +7,7 @@ pub mod models;
pub mod services; pub mod services;
pub mod routes; pub mod routes;
pub mod utils; pub mod utils;
pub mod workflow;
use axum::Router; use axum::Router;
use axum::http::{HeaderValue, Method}; use axum::http::{HeaderValue, Method};
@ -37,6 +39,10 @@ async fn main() -> anyhow::Result<()> {
let db = db::init_db().await?; let db = db::init_db().await?;
// initialize Redis connection
let redis_pool = redis::init_redis().await?;
redis::set_redis_pool(redis_pool)?;
// run migrations // run migrations
migration::Migrator::up(&db, None).await.expect("migration up"); migration::Migrator::up(&db, None).await.expect("migration up");

View File

@ -2,7 +2,7 @@ use axum::{http::HeaderMap, http::header::AUTHORIZATION};
use chrono::{Utc, Duration as ChronoDuration}; use chrono::{Utc, Duration as ChronoDuration};
use jsonwebtoken::{EncodingKey, DecodingKey, Header, Validation}; use jsonwebtoken::{EncodingKey, DecodingKey, Header, Validation};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::error::AppError; use crate::{error::AppError, redis::TokenRedis};
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims { pub struct Claims {
@ -37,6 +37,21 @@ impl<S> axum::extract::FromRequestParts<S> for AuthUser where S: Send + Sync + '
let secret = std::env::var("JWT_SECRET").map_err(|_| AppError::Unauthorized)?; let secret = std::env::var("JWT_SECRET").map_err(|_| AppError::Unauthorized)?;
let claims = decode_token(token, &secret)?; let claims = decode_token(token, &secret)?;
if claims.typ != "access" { return Err(AppError::Unauthorized); } if claims.typ != "access" { return Err(AppError::Unauthorized); }
// 验证token是否在Redis中存在可选添加环境变量控制是否启用Redis验证
let redis_validation_enabled = std::env::var("REDIS_TOKEN_VALIDATION")
.unwrap_or_else(|_| "true".to_string())
.parse::<bool>()
.unwrap_or(true);
if redis_validation_enabled {
let is_valid = TokenRedis::validate_access_token(token, claims.uid).await
.unwrap_or(false);
if !is_valid {
return Err(AppError::Unauthorized);
}
}
Ok(AuthUser { uid: claims.uid, username: claims.sub }) Ok(AuthUser { uid: claims.uid, username: claims.sub })
} }
} }

154
backend/src/redis.rs Normal file
View File

@ -0,0 +1,154 @@
use redis::{Client, AsyncCommands};
use redis::aio::ConnectionManager;
use once_cell::sync::OnceCell;
use crate::error::AppError;
pub type RedisPool = ConnectionManager;
static REDIS_POOL: OnceCell<RedisPool> = OnceCell::new();
/// 初始化Redis连接池
pub async fn init_redis() -> Result<RedisPool, AppError> {
let redis_url = std::env::var("REDIS_URL")
.unwrap_or_else(|_| "redis://:123456@127.0.0.1:6379/9".into());
tracing::info!("Connecting to Redis at: {}", redis_url.replace(":123456", ":***"));
let client = Client::open(redis_url)
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Failed to create Redis client: {}", e)))?;
let manager = ConnectionManager::new(client).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Failed to create Redis connection manager: {}", e)))?;
tracing::info!("Redis connection established successfully");
Ok(manager)
}
/// 获取Redis连接池
pub fn get_redis() -> Result<&'static RedisPool, AppError> {
REDIS_POOL.get().ok_or_else(|| AppError::Anyhow(anyhow::anyhow!("Redis pool not initialized")))
}
/// 设置Redis连接池
pub fn set_redis_pool(pool: RedisPool) -> Result<(), AppError> {
REDIS_POOL.set(pool)
.map_err(|_| AppError::Anyhow(anyhow::anyhow!("Redis pool already initialized")))
}
/// Redis工具函数
pub struct RedisHelper;
impl RedisHelper {
/// 设置带过期时间的键值对
pub async fn set_ex(key: &str, value: &str, expire_seconds: u64) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
tracing::debug!("Redis SET_EX: key={}, value_len={}, expire_seconds={}", key, value.len(), expire_seconds);
let _: String = conn.set_ex(key, value, expire_seconds).await
.map_err(|e| {
tracing::error!("Redis set_ex failed for key {}: {}", key, e);
AppError::Anyhow(anyhow::anyhow!("Redis set_ex failed: {}", e))
})?;
tracing::debug!("Redis SET_EX successful for key: {}", key);
Ok(())
}
/// 获取键值
pub async fn get(key: &str) -> Result<Option<String>, AppError> {
let mut conn = get_redis()?.clone();
let result: Option<String> = conn.get(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis get failed: {}", e)))?;
Ok(result)
}
/// 删除键
pub async fn del(key: &str) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
let _: i32 = conn.del(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis del failed: {}", e)))?;
Ok(())
}
/// 检查键是否存在
pub async fn exists(key: &str) -> Result<bool, AppError> {
let mut conn = get_redis()?.clone();
let result: bool = conn.exists(key).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis exists failed: {}", e)))?;
Ok(result)
}
/// 设置键的过期时间
pub async fn expire(key: &str, seconds: u64) -> Result<(), AppError> {
let mut conn = get_redis()?.clone();
let _: bool = conn.expire(key, seconds as i64).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis expire failed: {}", e)))?;
Ok(())
}
/// 删除用户相关的所有token
pub async fn del_user_tokens(user_id: i64) -> Result<(), AppError> {
let pattern = format!("token:*:user:{}", user_id);
let mut conn = get_redis()?.clone();
// 获取匹配的键
let keys: Vec<String> = conn.keys(&pattern).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis keys failed: {}", e)))?;
// 删除所有匹配的键
if !keys.is_empty() {
let _: i32 = conn.del(&keys).await
.map_err(|e| AppError::Anyhow(anyhow::anyhow!("Redis del multiple failed: {}", e)))?;
}
Ok(())
}
}
/// Token相关的Redis操作
pub struct TokenRedis;
impl TokenRedis {
/// 存储访问token
pub async fn store_access_token(token: &str, user_id: i64, expire_seconds: u64) -> Result<(), AppError> {
let key = format!("token:access:user:{}", user_id);
tracing::info!("Storing access token for user {} with key: {}, expires in {} seconds", user_id, key, expire_seconds);
RedisHelper::set_ex(&key, token, expire_seconds).await
}
/// 存储刷新token
pub async fn store_refresh_token(token: &str, user_id: i64, expire_seconds: u64) -> Result<(), AppError> {
let key = format!("token:refresh:user:{}", user_id);
tracing::info!("Storing refresh token for user {} with key: {}, expires in {} seconds", user_id, key, expire_seconds);
RedisHelper::set_ex(&key, token, expire_seconds).await
}
/// 验证访问token
pub async fn validate_access_token(token: &str, user_id: i64) -> Result<bool, AppError> {
let key = format!("token:access:user:{}", user_id);
let stored_token = RedisHelper::get(&key).await?;
Ok(stored_token.as_deref() == Some(token))
}
/// 验证刷新token
pub async fn validate_refresh_token(token: &str, user_id: i64) -> Result<bool, AppError> {
let key = format!("token:refresh:user:{}", user_id);
let stored_token = RedisHelper::get(&key).await?;
Ok(stored_token.as_deref() == Some(token))
}
/// 删除用户的访问token
pub async fn revoke_access_token(user_id: i64) -> Result<(), AppError> {
let key = format!("token:access:user:{}", user_id);
RedisHelper::del(&key).await
}
/// 删除用户的刷新token
pub async fn revoke_refresh_token(user_id: i64) -> Result<(), AppError> {
let key = format!("token:refresh:user:{}", user_id);
RedisHelper::del(&key).await
}
/// 删除用户的所有token
pub async fn revoke_all_tokens(user_id: i64) -> Result<(), AppError> {
RedisHelper::del_user_tokens(user_id).await
}
}

View File

@ -1,5 +1,5 @@
use sea_orm::{EntityTrait, ColumnTrait, QueryFilter, ActiveModelTrait, Set}; use sea_orm::{EntityTrait, ColumnTrait, QueryFilter, ActiveModelTrait, Set};
use crate::{db::Db, models::{user, refresh_token}, utils::password, error::AppError}; use crate::{db::Db, models::{user, refresh_token}, utils::password, error::AppError, redis::TokenRedis};
use chrono::{Utc, Duration, FixedOffset}; use chrono::{Utc, Duration, FixedOffset};
use sha2::{Sha256, Digest}; use sha2::{Sha256, Digest};
use sea_orm::ActiveValue::NotSet; use sea_orm::ActiveValue::NotSet;
@ -11,13 +11,38 @@ pub async fn login(db: &Db, username: String, password_plain: String) -> Result<
if u.status != 1 { return Err(AppError::Forbidden); } if u.status != 1 { return Err(AppError::Forbidden); }
let ok = password::verify_password(&password_plain, &u.password_hash).map_err(|_| AppError::Unauthorized)?; let ok = password::verify_password(&password_plain, &u.password_hash).map_err(|_| AppError::Unauthorized)?;
if !ok { return Err(AppError::Unauthorized); } if !ok { return Err(AppError::Unauthorized); }
let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username); let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username);
let refresh_claims = crate::middlewares::jwt::new_refresh_claims(u.id, &u.username); let refresh_claims = crate::middlewares::jwt::new_refresh_claims(u.id, &u.username);
let secret = std::env::var("JWT_SECRET").unwrap(); let secret = std::env::var("JWT_SECRET").unwrap();
let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?; let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?;
let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?; let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?;
// persist refresh token hash // 获取过期时间(秒)
let access_exp_secs = std::env::var("JWT_ACCESS_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1800);
let refresh_exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
// 先删除用户的所有旧token防止多重登录
if let Err(e) = TokenRedis::revoke_all_tokens(u.id).await {
tracing::warn!("Failed to revoke old tokens for user {}: {}", u.id, e);
}
// 存储新token到Redis
if let Err(e) = TokenRedis::store_access_token(&access, u.id, access_exp_secs as u64).await {
tracing::error!("Failed to store access token to Redis for user {}: {}", u.id, e);
// 不返回错误降级到仅使用JWT模式
} else {
tracing::info!("Successfully stored access token to Redis for user {}", u.id);
}
if let Err(e) = TokenRedis::store_refresh_token(&refresh, u.id, refresh_exp_secs as u64).await {
tracing::error!("Failed to store refresh token to Redis for user {}: {}", u.id, e);
// 不返回错误降级到仅使用JWT模式
} else {
tracing::info!("Successfully stored refresh token to Redis for user {}", u.id);
}
// persist refresh token hash to database (backup)
let mut hasher = Sha256::new(); hasher.update(refresh.as_bytes()); let mut hasher = Sha256::new(); hasher.update(refresh.as_bytes());
let token_hash = format!("{:x}", hasher.finalize()); let token_hash = format!("{:x}", hasher.finalize());
let exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600); let exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
@ -36,15 +61,26 @@ pub async fn login(db: &Db, username: String, password_plain: String) -> Result<
} }
pub async fn logout(db: &Db, uid: i64) -> Result<(), AppError> { pub async fn logout(db: &Db, uid: i64) -> Result<(), AppError> {
// 从 Redis 中删除所有 token
let _ = TokenRedis::revoke_all_tokens(uid).await;
// 从数据库中删除 refresh token
let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).exec(db).await?; let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).exec(db).await?;
Ok(()) Ok(())
} }
pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(String, String), AppError> { pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(String, String), AppError> {
// 验证Redis中的refresh token
let is_valid_redis = TokenRedis::validate_refresh_token(&old_refresh, uid).await.unwrap_or(false);
// 同时验证数据库中的token hash备用验证
let mut hasher = Sha256::new(); hasher.update(old_refresh.as_bytes()); let mut hasher = Sha256::new(); hasher.update(old_refresh.as_bytes());
let token_hash = format!("{:x}", hasher.finalize()); let token_hash = format!("{:x}", hasher.finalize());
let existing = refresh_token::Entity::find().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash.clone())).one(db).await?; let existing = refresh_token::Entity::find().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash.clone())).one(db).await?;
if existing.is_none() { return Err(AppError::Unauthorized); }
if !is_valid_redis && existing.is_none() {
return Err(AppError::Unauthorized);
}
let u = user::Entity::find_by_id(uid).one(db).await?.ok_or(AppError::Unauthorized)?; let u = user::Entity::find_by_id(uid).one(db).await?.ok_or(AppError::Unauthorized)?;
let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username); let access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username);
@ -53,6 +89,15 @@ pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(S
let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?; let access = crate::middlewares::jwt::encode_token(&access_claims, &secret)?;
let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?; let refresh = crate::middlewares::jwt::encode_token(&refresh_claims, &secret)?;
// 获取过期时间
let access_exp_secs = std::env::var("JWT_ACCESS_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1800);
let refresh_exp_secs = std::env::var("JWT_REFRESH_EXP_SECS").ok().and_then(|v| v.parse().ok()).unwrap_or(1209600);
// 更新Redis中的token
TokenRedis::store_access_token(&access, u.id, access_exp_secs as u64).await?;
TokenRedis::store_refresh_token(&refresh, u.id, refresh_exp_secs as u64).await?;
// 更新数据库中的refresh token
let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash)).exec(db).await?; let _ = refresh_token::Entity::delete_many().filter(refresh_token::Column::UserId.eq(uid)).filter(refresh_token::Column::TokenHash.eq(token_hash)).exec(db).await?;
let mut hasher2 = Sha256::new(); hasher2.update(refresh.as_bytes()); let mut hasher2 = Sha256::new(); hasher2.update(refresh.as_bytes());
let token_hash2 = format!("{:x}", hasher2.finalize()); let token_hash2 = format!("{:x}", hasher2.finalize());

1
backend/temp_hash.rs Normal file
View File

@ -0,0 +1 @@
use argon2::{Argon2, PasswordHasher}; use argon2::password_hash::{SaltString, rand_core::OsRng}; fn main() { let argon2 = Argon2::default(); let salt = SaltString::generate(&mut OsRng); let password_hash = argon2.hash_password(b"123456", &salt).unwrap().to_string(); println!("{}", password_hash); }