增加jwt、validator、bcrypto功能,增加用户接口、增加提取器以及中间件

This commit is contained in:
胡天 2024-02-07 17:34:19 +08:00
parent 3f1941667c
commit 43f3867630
30 changed files with 628 additions and 96 deletions

250
Cargo.lock generated
View File

@ -240,15 +240,42 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-extra"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "895ff42f72016617773af68fb90da2a9677d89c62338ec09162d4909d86fdd8f"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum-with-seaorm" name = "axum-with-seaorm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"askama", "askama",
"async-trait",
"axum", "axum",
"axum-extra",
"bcrypt",
"chrono", "chrono",
"config", "config",
"dotenvy", "dotenvy",
"jsonwebtoken",
"lazy_static",
"sea-orm", "sea-orm",
"serde", "serde",
"serde_json", "serde_json",
@ -301,6 +328,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bcrypt"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3"
dependencies = [
"base64 0.21.7",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.3.1" version = "0.3.1"
@ -348,6 +388,16 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.3.1" version = "1.3.1"
@ -380,9 +430,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]] [[package]]
name = "bytecheck" name = "bytecheck"
version = "0.6.11" version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
dependencies = [ dependencies = [
"bytecheck_derive", "bytecheck_derive",
"ptr_meta", "ptr_meta",
@ -391,9 +441,9 @@ dependencies = [
[[package]] [[package]]
name = "bytecheck_derive" name = "bytecheck_derive"
version = "0.6.11" version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -448,6 +498,16 @@ dependencies = [
"windows-targets 0.52.0", "windows-targets 0.52.0",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]] [[package]]
name = "config" name = "config"
version = "0.13.4" version = "0.13.4"
@ -787,8 +847,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -844,6 +906,30 @@ dependencies = [
"hashbrown 0.14.3", "hashbrown 0.14.3",
] ]
[[package]]
name = "headers"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -855,9 +941,9 @@ dependencies = [
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
[[package]] [[package]]
name = "hex" name = "hex"
@ -1025,6 +1111,12 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "if_chain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.2" version = "2.2.2"
@ -1046,6 +1138,15 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.1" version = "0.12.1"
@ -1063,9 +1164,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.67" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1081,6 +1182,21 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "jsonwebtoken"
version = "9.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4"
dependencies = [
"base64 0.21.7",
"js-sys",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1443,6 +1559,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pem"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310"
dependencies = [
"base64 0.21.7",
"serde",
]
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@ -1460,9 +1586,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.6" version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror", "thiserror",
@ -1471,9 +1597,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.7.6" version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@ -1481,9 +1607,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.7.6" version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@ -1494,9 +1620,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.7.6" version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
@ -1736,9 +1862,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "rend" name = "rend"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
dependencies = [ dependencies = [
"bytecheck", "bytecheck",
] ]
@ -1759,9 +1885,9 @@ dependencies = [
[[package]] [[package]]
name = "rkyv" name = "rkyv"
version = "0.7.43" version = "0.7.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"bytecheck", "bytecheck",
@ -1777,9 +1903,9 @@ dependencies = [
[[package]] [[package]]
name = "rkyv_derive" name = "rkyv_derive"
version = "0.7.43" version = "0.7.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1941,9 +2067,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm" name = "sea-orm"
version = "0.12.12" version = "0.12.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cbf88748872fa54192476d6d49d0775e208566a72656e267e45f6980b926c8d" checksum = "6632f499b80cc6aaa781b302e4c9fae663e0e3dcf2640e9d80034d5b10731efe"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
@ -1969,9 +2095,9 @@ dependencies = [
[[package]] [[package]]
name = "sea-orm-macros" name = "sea-orm-macros"
version = "0.12.12" version = "0.12.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0dbc880d47aa53c6a572e39c99402c7fad59b50766e51e0b0fc1306510b0555" checksum = "ec13bfb4c4aef208f68dbea970dd40d13830c868aa8dcb4e106b956e6bb4f2fa"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -2129,6 +2255,18 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -2495,13 +2633,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.9.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -2800,9 +2937,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.10.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode_categories" name = "unicode_categories"
@ -2855,6 +2992,33 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"url", "url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
dependencies = [
"if_chain",
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
"regex",
"syn 1.0.109",
"validator_types",
]
[[package]]
name = "validator_types"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
dependencies = [
"proc-macro2",
"syn 1.0.109",
] ]
[[package]] [[package]]
@ -2883,9 +3047,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -2893,9 +3057,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@ -2908,9 +3072,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -2918,9 +3082,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2931,9 +3095,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
@ -3121,9 +3285,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.37" version = "0.5.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View File

@ -18,4 +18,9 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time", "lo
serde_json = "1.0.113" serde_json = "1.0.113"
chrono = "0.4.33" chrono = "0.4.33"
state = "0.6.0" state = "0.6.0"
validator = "0.16.1" lazy_static = "1.4.0"
validator = { version = "0.16.1", features = ["derive"] }
jsonwebtoken = "9.2.0"
async-trait = "0.1.77"
bcrypt = "0.15.0"
axum-extra = { version = "0.9.2", features = ["typed-header"] }

View File

@ -1,3 +1,5 @@
jwt_secret = "123456"
[server] [server]
port = 9527 port = 9527
host = "0.0.0.0" host = "0.0.0.0"
@ -5,3 +7,4 @@ host = "0.0.0.0"
[mysql] [mysql]
max_cons = 5 max_cons = 5
dsn = "mysql://root:mysql123!%40%23@127.0.0.1:3306/study" dsn = "mysql://root:mysql123!%40%23@127.0.0.1:3306/study"

View File

@ -1,6 +0,0 @@
[web]
addr = "0.0.0.0:9527"
[mysql]
max_cons = 5
dns = "mysql://root:mysql123!%40%23@127.0.0.1:3306/study"

View File

@ -1,9 +1,15 @@
use crate::common::response::AppResponse;
#[derive(Debug)] #[derive(Debug)]
pub enum AppErrorType { pub enum AppErrorType {
System,
Config, Config,
Database, Database,
Notfound, Notfound,
Validate,
Template, Template,
Authorization,
Forbidden,
} }
type Cause = Box<dyn std::error::Error>; type Cause = Box<dyn std::error::Error>;
@ -38,7 +44,11 @@ impl AppError {
impl std::fmt::Display for AppError { impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self) let msg = match &self.error {
AppErrorItem::Cause(err) => err.to_string(),
AppErrorItem::Message(msg) => msg.to_string(),
};
write!(f, "{}", msg)
} }
} }
@ -62,13 +72,22 @@ impl From<sea_orm::DbErr> for AppError {
} }
} }
impl From<jsonwebtoken::errors::Error> for AppError {
fn from(err: jsonwebtoken::errors::Error) -> Self {
Self::from_err(Box::new(err), AppErrorType::Authorization)
}
}
impl From<validator::ValidationErrors> for AppError {
fn from(err: validator::ValidationErrors) -> Self {
Self::from_err(Box::new(err), AppErrorType::Validate)
}
}
impl axum::response::IntoResponse for AppError { impl axum::response::IntoResponse for AppError {
fn into_response(self) -> axum::response::Response { fn into_response(self) -> axum::response::Response {
let msg = match self.error { AppResponse::<()>::from_error(self).into_response()
AppErrorItem::Cause(err) => err.to_string(),
AppErrorItem::Message(msg) => msg.to_string(),
};
msg.into_response()
} }
} }

View File

@ -0,0 +1,37 @@
use async_trait::async_trait;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::StatusCode;
use axum::RequestPartsExt;
use axum_extra::headers::authorization::{Authorization as ExtraAuthorization, Bearer};
use axum_extra::TypedHeader;
use tracing::info;
use crate::common::err::{AppError, AppErrorType};
use crate::common::jwt::JwtClaims;
#[derive(Debug, Clone)]
pub struct Authorization(pub JwtClaims);
#[async_trait]
impl<S> FromRequestParts<S> for Authorization {
type Rejection = (StatusCode, AppError);
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let TypedHeader(ExtraAuthorization(bearer)) = parts
.extract::<TypedHeader<ExtraAuthorization<Bearer>>>()
.await
.map_err(|error| {
(StatusCode::UNAUTHORIZED, AppError::from_err(Box::new(error), AppErrorType::Authorization))
})?;
info!("Authorization from_request_parts");
let claims = JwtClaims::verify(bearer.token())
.map_err(|error| (StatusCode::UNAUTHORIZED, error))?;
Ok(Self(claims))
}
}

View File

@ -0,0 +1 @@
pub mod authorization;

65
src/common/jwt/mod.rs Normal file
View File

@ -0,0 +1,65 @@
use chrono::{Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use jsonwebtoken::errors::ErrorKind;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use crate::APPLICATION_CONTEXT;
use crate::common::err::{AppError, AppErrorType};
use crate::common::Result;
use crate::config::app::AppConfig;
lazy_static! {
pub static ref JWT_SECRET: String = {
let app_cfg = APPLICATION_CONTEXT.get::<AppConfig>();
app_cfg.jwt_secret.clone()
};
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct JwtClaims {
// 账号id
pub id: u32,
// 账号
pub email: String,
// 过期时间
pub exp: i64,
// 签名时间
pub iat: i64,
}
impl JwtClaims {
pub fn new(id: u32, email: String) -> Self {
let iat = Utc::now();
let exp = iat + Duration::hours(24);
Self {
id,
email,
exp: exp.timestamp(),
iat: iat.timestamp(),
}
}
pub fn sign(&self) -> Result<String> {
let encoding_key = EncodingKey::from_secret(JWT_SECRET.as_bytes());
jsonwebtoken::encode(&Header::default(), self, &encoding_key)
.map_err(AppError::from)
}
pub fn verify(token: &str) -> Result<JwtClaims> {
let decode_key = DecodingKey::from_secret(JWT_SECRET.as_bytes());
let validation = Validation::default();
match jsonwebtoken::decode::<JwtClaims>(token, &decode_key, &validation) {
Ok(c) => Ok(c.claims),
Err(e) => match e.kind() {
ErrorKind::InvalidToken => Err(AppError::from_msg("Token失效", AppErrorType::Authorization)),
ErrorKind::InvalidIssuer => Err(AppError::from_msg("InvalidIssuer", AppErrorType::Authorization)),
_ => Err(AppError::from_msg("InvalidToken other errors", AppErrorType::Authorization))
},
}
}
}

View File

@ -1,6 +1,10 @@
pub mod template; pub mod template;
pub mod err; pub mod err;
pub mod response; pub mod response;
pub mod jwt;
pub mod extract;
mod pagination;
pub mod util;
pub type Result<T> = std::result::Result<T, err::AppError>; pub type Result<T> = std::result::Result<T, err::AppError>;

15
src/common/pagination.rs Normal file
View File

@ -0,0 +1,15 @@
use serde::Serialize;
#[derive(Serialize, Debug)]
pub struct Paginate<T: Serialize> {
pub total: u32,
pub page: u32,
pub page_size: u32,
pub data: T,
}
impl<T: Serialize> Paginate<T> {
pub fn new(total: u32, page: u32, page_size: u32, data: T) -> Self {
Self { total, page, page_size, data }
}
}

View File

@ -1,7 +1,14 @@
use axum::http::{header, HeaderMap, StatusCode}; use axum::http::{header, HeaderMap, StatusCode};
use axum::response::Html; use axum::response::{Html, IntoResponse, Json, Response};
use serde::Serialize;
use crate::common::err::AppError;
use super::Result; use super::Result;
pub const CODE_SUCCESS: i8 = 0;
pub const CODE_FAIL: i8 = -1;
pub type HtmlResponse = Html<String>; pub type HtmlResponse = Html<String>;
pub type RedirectResponse = (StatusCode, HeaderMap, ()); pub type RedirectResponse = (StatusCode, HeaderMap, ());
@ -11,5 +18,45 @@ pub fn redirect(url: &str) -> Result<RedirectResponse> {
Ok((StatusCode::FOUND, header, ())) Ok((StatusCode::FOUND, header, ()))
} }
#[derive(Debug, Serialize, Clone)]
pub struct AppResponse<T: Serialize> {
pub code: Option<i8>,
pub msg: Option<String>,
pub data: Option<T>,
}
impl<T: Serialize> IntoResponse for AppResponse<T> {
fn into_response(self) -> Response {
Json(self).into_response()
}
}
impl<T: Serialize> AppResponse<T> {
pub fn from_data(data: T) -> Self {
Self {
code: Some(CODE_SUCCESS),
msg: Some("success".to_string()),
data: Some(data),
}
}
pub fn from_error(err: AppError) -> Self {
Self {
code: Some(CODE_FAIL),
msg: Some(err.to_string()),
data: None,
}
}
pub fn from_result(result: Result<T>) -> Self {
match result {
Ok(data) => Self::from_data(data),
Err(err) => Self::from_error(err),
}
}
}

View File

@ -0,0 +1,28 @@
pub mod default_datetime_format {
use chrono::{DateTime, Local, NaiveDateTime};
use serde::{Deserialize, Deserializer, Serializer};
const FORMAT: &'static str = "%Y-%m-%d %H:%M:%S";
pub fn serialize<S>(
date: &DateTime<Local>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = format!("{}", date.format(FORMAT));
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<DateTime<Local>, D::Error>
where
D: Deserializer<'de>,
{
let datetime_str = String::deserialize(deserializer)?;
let dt = NaiveDateTime::parse_from_str(&datetime_str, FORMAT).map_err(serde::de::Error::custom)?;
Ok(dt.and_local_timezone(Local).unwrap())
}
}

1
src/common/util/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod datetime_format;

View File

@ -7,4 +7,5 @@ use crate::config::server::ServerConfig;
pub struct AppConfig { pub struct AppConfig {
pub mysql: MysqlConfig, pub mysql: MysqlConfig,
pub server: ServerConfig, pub server: ServerConfig,
pub jwt_secret: String,
} }

View File

@ -1,4 +1,5 @@
pub mod category; pub mod category;
pub mod article; pub mod article;
pub mod article_tag; pub mod article_tag;
pub mod tag; pub mod tag;
pub mod users;

22
src/entity/users.rs Normal file
View File

@ -0,0 +1,22 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.12
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: u32,
pub email: String,
#[serde(skip_serializing)]
pub password_hash: String,
pub create_time: DateTimeLocal,
#[serde(skip_serializing)]
pub is_del: bool,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,12 +1,23 @@
use serde::Deserialize; use serde::Deserialize;
use validator::{Validate};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CategoryForm { pub struct CategoryForm {
pub name: String, pub name: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ArticleForm { pub struct ArticleForm {
pub title: String, pub title: String,
pub category_id: u32, pub category_id: u32,
pub content: String, pub content: String,
}
#[derive(Validate, Deserialize)]
pub struct UserSignForm {
#[validate(email(message = "请输入正确格式的邮箱"))]
pub email: String,
#[validate(length(min = 6, message = "密码长度至少为 6"))]
pub password: String,
} }

View File

@ -1,6 +1,5 @@
use axum::{Form, Router};
use axum::extract::Query; use axum::extract::Query;
use axum::routing::get; use axum::Form;
use crate::common::{response::HtmlResponse, Result}; use crate::common::{response::HtmlResponse, Result};
use crate::common::response::{redirect, RedirectResponse}; use crate::common::response::{redirect, RedirectResponse};
@ -9,13 +8,6 @@ use crate::form::ArticleForm;
use crate::param::ArticleParams; use crate::param::ArticleParams;
use crate::service::article::ArticleService; use crate::service::article::ArticleService;
pub fn init_router() -> Router {
Router::new()
.route("/", get(index))
.route("/add", get(add_ui).post(add))
.route("/tags", get(list_with_tags))
}
pub async fn index(Query(params): Query<ArticleParams>) -> Result<HtmlResponse> { pub async fn index(Query(params): Query<ArticleParams>) -> Result<HtmlResponse> {
let handler_name = "article/index"; let handler_name = "article/index";
let tpl = ArticleService::index(handler_name, params).await?; let tpl = ArticleService::index(handler_name, params).await?;

View File

@ -1,6 +1,5 @@
use axum::{Form, Router};
use axum::extract::{Path, Query}; use axum::extract::{Path, Query};
use axum::routing::get; use axum::Form;
use crate::{form, view}; use crate::{form, view};
use crate::common::{response::HtmlResponse, Result}; use crate::common::{response::HtmlResponse, Result};
@ -9,22 +8,6 @@ use crate::common::template::render;
use crate::param::{CategoryParams, DelParams}; use crate::param::{CategoryParams, DelParams};
use crate::service::category::CategoryService; use crate::service::category::CategoryService;
pub fn init_router() -> Router {
Router::new()
.route("/", get(index))
.route(
"/add",
get(add_ui).post(add),
)
.route(
"/edit/:id",
get(edit_ui).post(edit),
)
.route("/del/:id", get(del))
.route("/del/:id/:real", get(del))
.route("/articles/:id", get(articles))
}
pub async fn index(Query(params): Query<CategoryParams>) -> Result<HtmlResponse> { pub async fn index(Query(params): Query<CategoryParams>) -> Result<HtmlResponse> {
let handler_name = "category/index"; let handler_name = "category/index";
let tpl = CategoryService::index(handler_name, params).await?; let tpl = CategoryService::index(handler_name, params).await?;

View File

@ -4,4 +4,5 @@ pub mod category;
mod index; mod index;
pub mod article; pub mod article;
pub mod user;

22
src/handler/user.rs Normal file
View File

@ -0,0 +1,22 @@
use axum::Json;
use crate::common::extract::authorization::Authorization;
use crate::common::response::AppResponse;
use crate::common::Result;
use crate::entity::users;
use crate::form::UserSignForm;
use crate::service::user::UserService;
pub async fn userinfo(Authorization(jwt_claims): Authorization) -> Result<AppResponse<users::Model>> {
let user = UserService::userinfo(jwt_claims.id).await?;
Ok(AppResponse::from_data(user))
}
pub async fn sign_in(Json(jsn): Json<UserSignForm>) -> Result<String> {
UserService::sign_in(jsn).await
}
pub async fn list() -> Result<AppResponse<Vec<users::Model>>> {
let user = UserService::list().await?;
Ok(AppResponse::from_data(user))
}

View File

@ -1,7 +1,6 @@
use ::state::type_map::TypeMapSendSync; use ::state::type_map::TypeMapSendSync;
use tracing::info; use tracing::info;
pub mod state;
pub mod entity; pub mod entity;
// pub mod state; // pub mod state;
pub mod handler; pub mod handler;
@ -13,6 +12,7 @@ pub mod config;
pub mod router; pub mod router;
pub mod initialize; pub mod initialize;
pub mod common; pub mod common;
pub mod middleware;
/// 整个项目上下文 ApplicationContext /// 整个项目上下文 ApplicationContext

0
src/middleware/mod.rs Normal file
View File

11
src/router/article.rs Normal file
View File

@ -0,0 +1,11 @@
use axum::Router;
use axum::routing::get;
use crate::handler::article::{add, add_ui, index, list_with_tags};
pub fn init_router() -> Router {
Router::new()
.route("/", get(index))
.route("/add", get(add_ui).post(add))
.route("/tags", get(list_with_tags))
}

20
src/router/category.rs Normal file
View File

@ -0,0 +1,20 @@
use axum::Router;
use axum::routing::get;
use crate::handler::category::{add, add_ui, articles, del, edit, edit_ui, index};
pub fn init_router() -> Router {
Router::new()
.route("/", get(index))
.route(
"/add",
get(add_ui).post(add),
)
.route(
"/edit/:id",
get(edit_ui).post(edit),
)
.route("/del/:id", get(del))
.route("/del/:id/:real", get(del))
.route("/articles/:id", get(articles))
}

View File

@ -1,13 +1,18 @@
mod article;
mod category;
mod user;
use axum::routing::get; use axum::routing::get;
use crate::handler; use crate::handler::{index};
use crate::handler::{category, article};
pub fn init() -> axum::Router { pub fn init() -> axum::Router {
let category_router = category::init_router(); let category_router = category::init_router();
let article_router = article::init_router(); let article_router = article::init_router();
let user_router = user::init_router();
axum::Router::new() axum::Router::new()
.route("/", get(handler::index)) .route("/", get(index))
.nest("/category", category_router) .nest("/category", category_router)
.nest("/article", article_router) .nest("/article", article_router)
.nest("/user", user_router)
} }

24
src/router/user.rs Normal file
View File

@ -0,0 +1,24 @@
use axum::middleware::from_extractor;
use axum::Router;
use axum::routing::{get, post};
use crate::common::extract::authorization::Authorization;
use crate::handler::user::{list, sign_in, userinfo};
pub fn init_router() -> Router {
need_auth_routers().merge(not_need_auth_routers())
}
pub fn need_auth_routers() -> Router {
Router::new()
// 在此之前的利用中间价鉴权
.layer(from_extractor::<Authorization>())
// 之后的利用提取器鉴权
.route("/userinfo", get(userinfo))
}
pub fn not_need_auth_routers() -> Router {
Router::new()
.route("/sign_in", post(sign_in))
.route("/list", get(list))
}

View File

@ -1,2 +1,3 @@
pub mod article; pub mod article;
pub mod category; pub mod category;
pub mod user;

61
src/service/user.rs Normal file
View File

@ -0,0 +1,61 @@
use bcrypt::DEFAULT_COST;
use chrono::Local;
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter};
use sea_orm::ActiveValue::Set;
use validator::{Validate};
use crate::APPLICATION_CONTEXT;
use crate::common::{err::AppError, Result};
use crate::common::err::AppErrorType;
use crate::common::jwt::JwtClaims;
use crate::entity::users;
use crate::form::UserSignForm;
pub struct UserService;
impl UserService {
pub async fn list() -> Result<Vec<users::Model>> {
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
users::Entity::find().all(conn).await.map_err(AppError::from)
}
pub async fn userinfo(id: u32) -> Result<users::Model> {
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
let user = users::Entity::find_by_id(id).one(conn).await.map_err(AppError::from)?;
match user {
None => Err(AppError::notfound()),
Some(user) => Ok(user),
}
}
pub async fn sign_in(jsn: UserSignForm) -> Result<String> {
jsn.validate().map_err(AppError::from)?;
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
let condition = Condition::all().add(users::Column::Email.eq(&jsn.email));
let user = users::Entity::find().filter(condition).one(conn).await?;
if user.is_some() {
return Err(AppError::from_msg("用户已存在", AppErrorType::Database));
}
let hashed_password = bcrypt::hash(&jsn.password, DEFAULT_COST).unwrap();
let user = users::ActiveModel {
email: Set(jsn.email),
password_hash: Set(hashed_password),
create_time: Set(Local::now()),
..Default::default()
};
let user = user.save(conn).await.map_err(AppError::from)?;
let jwt_token = JwtClaims::new(user.id.unwrap(), user.email.unwrap()).sign();
jwt_token
}
}

View File

@ -1,6 +0,0 @@
use sea_orm::DatabaseConnection;
pub struct AppState {
pub conn: DatabaseConnection,
}