diff --git a/backend/.env b/backend/.env index 3938782..a401836 100644 --- a/backend/.env +++ b/backend/.env @@ -1,10 +1,14 @@ RUST_LOG=info,udmin=debug APP_ENV=development APP_HOST=0.0.0.0 -APP_PORT=9898 +APP_PORT=9891 DB_URL=mysql://root:123456@127.0.0.1:3306/udmin JWT_SECRET=dev_secret_change_me JWT_ISS=udmin JWT_ACCESS_EXP_SECS=1800 JWT_REFRESH_EXP_SECS=1209600 -CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost:5174,http://localhost:5175 \ No newline at end of file +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 \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index 4efd4b2..418a5af 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,10 +1,22 @@ -RUST_LOG=info,udmin=debug -APP_ENV=development -APP_HOST=0.0.0.0 -APP_PORT=9898 -DB_URL=mysql://root:123456@127.0.0.1:3306/udmin -JWT_SECRET=please_change_me +# 数据库配置 +DATABASE_URL=sqlite://udmin.db?mode=rwc + +# JWT配置 +JWT_SECRET=your-super-secret-jwt-key-change-this-in-production JWT_ISS=udmin -JWT_ACCESS_EXP_SECS=1800 -JWT_REFRESH_EXP_SECS=1209600 -CORS_ALLOW_ORIGINS=http://localhost:5173 \ No newline at end of file +JWT_ACCESS_EXP_SECS=1800 # 30分钟 +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 + +# 日志级别 +RUST_LOG=info \ No newline at end of file diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8d83840..e1597b3 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -35,6 +35,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -132,6 +134,12 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "argon2" version = "0.5.3" @@ -220,10 +228,10 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.7.0", "hyper-util", "itoa", "matchit 0.7.3", @@ -236,7 +244,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -254,10 +262,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.7.0", "hyper-util", "itoa", "matchit 0.8.4", @@ -270,7 +278,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -287,13 +295,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -307,18 +315,27 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "backon" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" +dependencies = [ + "fastrand", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -366,6 +383,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.2" @@ -550,6 +573,20 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "concurrent-queue" version = "2.5.0" @@ -613,6 +650,16 @@ dependencies = [ "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]] name = "core-foundation-sys" version = "0.8.7" @@ -829,6 +876,18 @@ dependencies = [ "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]] name = "flate2" version = "1.1.2" @@ -885,6 +944,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -935,6 +995,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "futures-sink" version = "0.3.31" @@ -953,8 +1024,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1010,6 +1083,25 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "hashbrown" version = "0.12.3" @@ -1103,6 +1195,17 @@ dependencies = [ "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]] name = "http" version = "1.3.1" @@ -1114,6 +1217,17 @@ dependencies = [ "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]] name = "http-body" version = "1.0.1" @@ -1121,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -1132,8 +1246,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -1149,6 +1263,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "hyper" version = "1.7.0" @@ -1159,8 +1297,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1170,6 +1308,20 @@ dependencies = [ "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]] name = "hyper-util" version = "0.1.16" @@ -1178,9 +1330,9 @@ checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "bytes", "futures-core", - "http", - "http-body", - "hyper", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.7.0", "pin-project-lite", "tokio", "tower-service", @@ -1356,23 +1508,47 @@ dependencies = [ "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]] name = "io-uring" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ - "bitflags", + "bitflags 2.9.2", "cfg-if", "libc", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1442,7 +1618,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags", + "bitflags 2.9.2", "libc", "redox_syscall", ] @@ -1666,6 +1842,9 @@ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -1837,6 +2016,16 @@ dependencies = [ "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]] name = "pgvector" version = "0.4.1" @@ -1885,6 +2074,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "potential_utf" version = "0.1.2" @@ -2057,13 +2252,39 @@ dependencies = [ "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]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags", + "bitflags 2.9.2", ] [[package]] @@ -2139,6 +2360,77 @@ dependencies = [ "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]] name = "ring" version = "0.17.14" @@ -2189,7 +2481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags", + "bitflags 2.9.2", "serde", "serde_derive", ] @@ -2280,6 +2572,18 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "rustls" version = "0.23.31" @@ -2289,11 +2593,20 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.4", "subtle", "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]] name = "rustls-pki-types" version = "1.12.0" @@ -2303,6 +2616,16 @@ dependencies = [ "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]] name = "rustls-webpki" version = "0.103.4" @@ -2365,6 +2688,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "sea-bae" version = "0.2.1" @@ -2624,6 +2957,19 @@ dependencies = [ "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]] name = "sha1" version = "0.10.6" @@ -2635,6 +2981,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -2713,6 +3065,28 @@ dependencies = [ "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]] name = "socket2" version = "0.6.0" @@ -2781,7 +3155,7 @@ dependencies = [ "once_cell", "percent-encoding", "rust_decimal", - "rustls", + "rustls 0.23.31", "serde", "serde_json", "sha2", @@ -2843,7 +3217,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags", + "bitflags 2.9.2", "byteorder", "bytes", "chrono", @@ -2890,7 +3264,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags", + "bitflags 2.9.2", "byteorder", "chrono", "crc", @@ -3014,6 +3388,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3031,12 +3411,42 @@ dependencies = [ "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]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "thiserror" version = "1.0.69" @@ -3166,7 +3576,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] @@ -3182,6 +3592,16 @@ dependencies = [ "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]] name = "tokio-stream" version = "0.1.17" @@ -3193,6 +3613,19 @@ dependencies = [ "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]] name = "toml" version = "0.8.23" @@ -3243,7 +3676,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -3256,10 +3689,10 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags", + "bitflags 2.9.2", "bytes", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", "tower-layer", "tower-service", @@ -3340,6 +3773,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.18.0" @@ -3364,15 +3803,21 @@ dependencies = [ "chrono", "config", "dotenvy", - "hyper", + "hyper 1.7.0", "jsonwebtoken", "migration", "once_cell", + "petgraph", "rand", + "redis", + "regex", + "reqwest", + "rhai", "sea-orm", "serde", "serde_json", "serde_with", + "serde_yaml", "sha2", "thiserror 1.0.69", "tokio", @@ -3424,6 +3869,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -3558,6 +4009,15 @@ dependencies = [ "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]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3605,6 +4065,19 @@ dependencies = [ "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]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -3637,6 +4110,22 @@ dependencies = [ "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]] name = "webpki-roots" version = "0.26.11" @@ -3986,13 +4475,23 @@ dependencies = [ "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]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.2", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2e0daa5..06f27da 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -2,6 +2,7 @@ name = "udmin" version = "0.1.0" edition = "2024" # ✅ 升级到最新 Rust Edition +default-run = "udmin" [dependencies] axum = "0.8.4" @@ -30,6 +31,14 @@ utoipa-swagger-ui = { version = "6.0.0", features = ["axum"] } sha2 = "0.10" rand = "0.8" 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] path = "migration" diff --git a/backend/REDIS_INTEGRATION.md b/backend/REDIS_INTEGRATION.md new file mode 100644 index 0000000..a50b6af --- /dev/null +++ b/backend/REDIS_INTEGRATION.md @@ -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过期时间(秒) \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index d62b413..2bc1b01 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod db; +mod redis; mod response; mod error; pub mod middlewares; @@ -6,6 +7,7 @@ pub mod models; pub mod services; pub mod routes; pub mod utils; +pub mod workflow; use axum::Router; use axum::http::{HeaderValue, Method}; @@ -37,6 +39,10 @@ async fn main() -> anyhow::Result<()> { let db = db::init_db().await?; + // initialize Redis connection + let redis_pool = redis::init_redis().await?; + redis::set_redis_pool(redis_pool)?; + // run migrations migration::Migrator::up(&db, None).await.expect("migration up"); diff --git a/backend/src/middlewares/jwt.rs b/backend/src/middlewares/jwt.rs index da6d469..91d902b 100644 --- a/backend/src/middlewares/jwt.rs +++ b/backend/src/middlewares/jwt.rs @@ -2,7 +2,7 @@ use axum::{http::HeaderMap, http::header::AUTHORIZATION}; use chrono::{Utc, Duration as ChronoDuration}; use jsonwebtoken::{EncodingKey, DecodingKey, Header, Validation}; use serde::{Serialize, Deserialize}; -use crate::error::AppError; +use crate::{error::AppError, redis::TokenRedis}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { @@ -37,6 +37,21 @@ impl axum::extract::FromRequestParts for AuthUser where S: Send + Sync + ' let secret = std::env::var("JWT_SECRET").map_err(|_| AppError::Unauthorized)?; let claims = decode_token(token, &secret)?; 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::() + .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 }) } } diff --git a/backend/src/redis.rs b/backend/src/redis.rs new file mode 100644 index 0000000..59a73f0 --- /dev/null +++ b/backend/src/redis.rs @@ -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 = OnceCell::new(); + +/// 初始化Redis连接池 +pub async fn init_redis() -> Result { + 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, AppError> { + let mut conn = get_redis()?.clone(); + let result: Option = 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 { + 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 = 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 { + 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 { + 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 + } +} \ No newline at end of file diff --git a/backend/src/services/auth_service.rs b/backend/src/services/auth_service.rs index 9f6fdab..8863991 100644 --- a/backend/src/services/auth_service.rs +++ b/backend/src/services/auth_service.rs @@ -1,5 +1,5 @@ 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 sha2::{Sha256, Digest}; 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); } let ok = password::verify_password(&password_plain, &u.password_hash).map_err(|_| AppError::Unauthorized)?; if !ok { return Err(AppError::Unauthorized); } + 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 secret = std::env::var("JWT_SECRET").unwrap(); let access = crate::middlewares::jwt::encode_token(&access_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 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); @@ -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> { + // 从 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?; Ok(()) } 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 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?; - 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 access_claims = crate::middlewares::jwt::new_access_claims(u.id, &u.username); @@ -52,7 +88,16 @@ pub async fn rotate_refresh(db: &Db, uid: i64, old_refresh: String) -> Result<(S let secret = std::env::var("JWT_SECRET").unwrap(); let access = crate::middlewares::jwt::encode_token(&access_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 mut hasher2 = Sha256::new(); hasher2.update(refresh.as_bytes()); let token_hash2 = format!("{:x}", hasher2.finalize()); diff --git a/backend/temp_hash.rs b/backend/temp_hash.rs new file mode 100644 index 0000000..a7c0b03 --- /dev/null +++ b/backend/temp_hash.rs @@ -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); }