Compare commits

..

6 Commits

Author SHA1 Message Date
soul-walker f90e4048ef 修改gitea-actions
build / build-rust (push) Failing after 5m47s Details
2024-10-31 15:45:02 +08:00
soul-walker 50ed464ff2 submodule 更新 2024-10-31 15:43:01 +08:00
soul-walker 505230b26c 1 重构rtsa_mqtt库,添加路由请求响应功能
2 修改库命名rtss->rtsa
2024-10-31 15:12:26 +08:00
soul-walker 2e1eecfdad 项目结构大调整 2024-10-30 09:47:31 +08:00
soul-walker 4d8c0e0bad 同步submodule 2024-10-23 09:16:10 +08:00
soul-walker 7226904e04 1, 添加创建更新城市轨道交通仿真功能接口
2, 添加mqtt客户端crate,提供订阅、发布、发送请求等功能
2024-10-22 17:24:50 +08:00
111 changed files with 3211 additions and 1678 deletions

View File

@ -50,7 +50,7 @@ jobs:
file: ./Dockerfile
push: true
tags: |
gitea.joylink.club/joylink/rtss-simulation:local-test
gitea.joylink.club/joylink/rtsam:lt
- name: 发布到本地测试环境
uses: https://gitea.joylink.club/appleboy/ssh-action@v1.0.3
with:
@ -59,6 +59,6 @@ jobs:
username: ${{ secrets.LOCAL_233_SSH_USER }}
password: ${{ secrets.LOCAL_233_SSH_PASSWORD }}
script: |
docker rm -f rtss-simulation || echo "rtss-simulation not exist"
docker pull gitea.joylink.club/joylink/rtss-simulation:local-test
docker run --name rtss-simulation --restart=always -e RUN_MODE=local_test --network jlnet --ip 10.11.11.11 -d -p 8765:8765 -v /usr/local/joylink/logs/simulation:/logs/simulation gitea.joylink.club/joylink/rtss-simulation:local-test
docker rm -f rtsa-manage || echo "rtsa-manage not exist"
docker pull gitea.joylink.club/joylink/rtsam:lt
docker run --name rtsa-manage --restart=always -e RUN_MODE=local_test --network jlnet --ip 10.11.11.11 -d -p 8765:8765 -v /usr/local/joylink/logs/simulation:/logs/simulation gitea.joylink.club/joylink/rtsam:lt

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
.env

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "rtss-proto-msg"]
path = rtss-proto-msg
url = https://gitea.joylink.club/joylink/rtss-proto-msg.git
[submodule "rtsa-proto-msg"]
path = rtsa-proto-msg
url = https://gitea.joylink.club/joylink/rtsa-proto-msg.git

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/<executable file>",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

12
.vscode/settings.json vendored
View File

@ -2,6 +2,8 @@
"cSpell.words": [
"chrono",
"cpus",
"dashmap",
"eventloop",
"Graphi",
"graphiql",
"hashbrown",
@ -11,7 +13,12 @@
"Joylink",
"jsonwebtoken",
"mplj",
"Mqtt",
"mqttbytes",
"Neng",
"nextval",
"oneshot",
"otype",
"plpgsql",
"prost",
"proto",
@ -19,7 +26,10 @@
"protos",
"repr",
"reqwest",
"rtss",
"rtsa",
"rtsa",
"rumqtt",
"rumqttc",
"sqlx",
"sysinfo",
"thiserror",

383
Cargo.lock generated
View File

@ -121,9 +121,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.88"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "ascii_utils"
@ -133,9 +139,9 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
[[package]]
name = "async-executor"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
dependencies = [
"async-task",
"concurrent-queue",
@ -270,9 +276,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.81"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@ -325,7 +331,7 @@ dependencies = [
"sync_wrapper 1.0.1",
"tokio",
"tokio-tungstenite",
"tower",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
@ -369,7 +375,7 @@ dependencies = [
"mime",
"pin-project-lite",
"serde",
"tower",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
@ -564,7 +570,7 @@ dependencies = [
"ahash",
"bevy_utils_proc_macros",
"getrandom",
"hashbrown 0.14.5",
"hashbrown",
"thread_local",
"tracing",
"web-time",
@ -619,9 +625,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
dependencies = [
"serde",
]
@ -658,9 +664,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.17"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
@ -668,9 +674,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.17"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstream",
"anstyle",
@ -680,9 +686,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.13"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
@ -713,14 +719,13 @@ dependencies = [
[[package]]
name = "config"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf"
dependencies = [
"async-trait",
"convert_case",
"json5",
"lazy_static",
"nom",
"pathdiff",
"ron",
@ -728,7 +733,7 @@ dependencies = [
"serde",
"serde_json",
"toml",
"yaml-rust",
"yaml-rust2",
]
[[package]]
@ -776,6 +781,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"
@ -1066,9 +1081,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flume"
version = "0.11.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
@ -1236,12 +1251,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1253,13 +1262,22 @@ dependencies = [
"serde",
]
[[package]]
name = "hashlink"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown",
]
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown 0.14.5",
"hashbrown",
]
[[package]]
@ -1407,10 +1425,10 @@ dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls 0.23.13",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.0",
"tower-service",
"webpki-roots",
]
@ -1430,7 +1448,7 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower 0.4.13",
"tower-service",
"tracing",
]
@ -1481,7 +1499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [
"equivalent",
"hashbrown 0.14.5",
"hashbrown",
"serde",
]
@ -1579,12 +1597,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@ -1613,7 +1625,22 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
dependencies = [
"hashbrown 0.14.5",
"hashbrown",
]
[[package]]
name = "manager"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"config",
"enum_dispatch",
"rtsa_api",
"rtsa_db",
"rtsa_log",
"serde",
"tokio",
]
[[package]]
@ -1817,13 +1844,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "ordered-multimap"
version = "0.6.0"
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "ordered-multimap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
"dlv-list",
"hashbrown 0.13.2",
"hashbrown",
]
[[package]]
@ -2119,7 +2152,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"rustls 0.23.13",
"socket2",
"thiserror",
"tokio",
@ -2136,7 +2169,7 @@ dependencies = [
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls 0.23.13",
"slab",
"thiserror",
"tinyvec",
@ -2301,7 +2334,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls 0.23.13",
"rustls-pemfile",
"rustls-pki-types",
"serde",
@ -2309,7 +2342,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.0",
"tower-service",
"url",
"wasm-bindgen",
@ -2367,7 +2400,7 @@ dependencies = [
]
[[package]]
name = "rtss_api"
name = "rtsa_api"
version = "0.1.0"
dependencies = [
"anyhow",
@ -2376,15 +2409,12 @@ dependencies = [
"axum",
"axum-extra",
"base64 0.22.1",
"bevy_ecs",
"chrono",
"jsonwebtoken",
"reqwest",
"rtss_db",
"rtss_dto",
"rtss_log",
"rtss_sim_manage",
"rtss_trackside",
"rtsa_db",
"rtsa_dto",
"rtsa_log",
"serde",
"serde_json",
"sysinfo",
@ -2393,23 +2423,13 @@ dependencies = [
]
[[package]]
name = "rtss_ci"
version = "0.1.0"
[[package]]
name = "rtss_common"
version = "0.1.0"
dependencies = [
"bevy_ecs",
]
[[package]]
name = "rtss_db"
name = "rtsa_db"
version = "0.1.0"
dependencies = [
"anyhow",
"rtss_dto",
"rtss_log",
"lazy_static",
"rtsa_dto",
"rtsa_log",
"serde",
"serde_json",
"sqlx",
@ -2417,22 +2437,19 @@ dependencies = [
]
[[package]]
name = "rtss_dto"
name = "rtsa_dto"
version = "0.1.0"
dependencies = [
"async-graphql",
"prost",
"prost-build",
"prost-types",
"serde",
"sqlx",
]
[[package]]
name = "rtss_iscs"
version = "0.1.0"
[[package]]
name = "rtss_log"
name = "rtsa_log"
version = "0.1.0"
dependencies = [
"tracing",
@ -2441,52 +2458,43 @@ dependencies = [
]
[[package]]
name = "rtss_sim_manage"
name = "rtsa_mqtt"
version = "0.1.0"
dependencies = [
"bevy_app",
"bevy_core",
"bevy_ecs",
"bevy_time",
"rayon",
"rtss_common",
"rtss_log",
"rtss_trackside",
"async-trait",
"bytes",
"lazy_static",
"rtsa_log",
"rumqttc",
"thiserror",
]
[[package]]
name = "rtss_simulation"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"config",
"enum_dispatch",
"rtss_api",
"rtss_db",
"rtss_log",
"serde",
"tokio",
"tower 0.5.1",
]
[[package]]
name = "rtss_trackside"
version = "0.1.0"
name = "rumqttc"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9"
dependencies = [
"bevy_app",
"bevy_core",
"bevy_ecs",
"bevy_time",
"rtss_common",
"rtss_log",
"bytes",
"flume",
"futures-util",
"log",
"rustls-native-certs",
"rustls-pemfile",
"rustls-webpki",
"thiserror",
"tokio",
"tokio-rustls 0.25.0",
"url",
]
[[package]]
name = "rust-ini"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a"
dependencies = [
"cfg-if",
"ordered-multimap",
@ -2517,6 +2525,20 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.13"
@ -2531,6 +2553,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.3"
@ -2570,6 +2605,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -2577,19 +2621,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.210"
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
@ -2598,9 +2665,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.125"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
@ -2698,6 +2765,29 @@ dependencies = [
"time",
]
[[package]]
name = "simulation"
version = "0.1.0"
dependencies = [
"anyhow",
"bevy_app",
"bevy_core",
"bevy_ecs",
"bevy_time",
"clap",
"config",
"enum_dispatch",
"lazy_static",
"rayon",
"rtsa_db",
"rtsa_dto",
"rtsa_log",
"rtsa_mqtt",
"serde",
"thiserror",
"tokio",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -2796,8 +2886,8 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashlink",
"hashbrown",
"hashlink 0.9.1",
"hex",
"indexmap",
"log",
@ -3019,9 +3109,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.76"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
@ -3072,18 +3162,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.63"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
@ -3157,9 +3247,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.40.0"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
dependencies = [
"backtrace",
"bytes",
@ -3182,13 +3272,24 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls",
"rustls 0.23.13",
"rustls-pki-types",
"tokio",
]
@ -3291,6 +3392,20 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper 0.1.2",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.0"
@ -3417,9 +3532,9 @@ dependencies = [
[[package]]
name = "typeid"
version = "1.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
[[package]]
name = "typenum"
@ -3935,12 +4050,14 @@ dependencies = [
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
dependencies = [
"linked-hash-map",
"arraydeque",
"encoding_rs",
"hashlink 0.8.4",
]
[[package]]

View File

@ -1,12 +1,7 @@
[package]
name = "rtss_simulation"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["crates/*"]
members = ["crates/*", "manager/crates/*", "manager", "simulation"]
resolver = "2"
[workspace.dependencies]
bevy_app = "0.14"
@ -14,25 +9,21 @@ bevy_core = "0.14"
bevy_ecs = "0.14"
bevy_time = "0.14"
rayon = "1.10"
tokio = { version = "1.40", features = ["macros", "rt-multi-thread"] }
thiserror = "1.0"
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
thiserror = "1.0.65"
sqlx = { version = "0.8", features = [
"runtime-tokio",
"postgres",
"json",
"chrono",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.125"
anyhow = "1.0"
[dependencies]
tokio = { version = "1.39.3", features = ["macros", "rt-multi-thread"] }
rtss_log = { path = "crates/rtss_log" }
rtss_api = { path = "crates/rtss_api" }
rtss_db = { path = "crates/rtss_db" }
serde = { workspace = true }
config = "0.14.0"
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
anyhow = "1.0.91"
async-trait = "0.1.83"
bytes = "1.8.0"
lazy_static = "1.5.0"
config = "0.14.1"
clap = { version = "4.5.20", features = ["derive"] }
enum_dispatch = "0.3"
anyhow = { workspace = true }
tower = { version = "0.5.1", features = ["util"] }

View File

@ -1,4 +1,4 @@
[default.extend-words]
[files]
extend-exclude = ["README.md", "rtss-proto-msg/*"]
extend-exclude = ["README.md", "rtsa-proto-msg/*"]

View File

@ -1,5 +1,5 @@
[package]
name = "rtss_db"
name = "rtsa_db"
version = "0.1.0"
edition = "2021"
@ -17,6 +17,7 @@ sqlx = { workspace = true, features = [
"uuid",
] }
thiserror = { workspace = true }
lazy_static = { workspace = true }
rtss_dto = { path = "../rtss_dto" }
rtss_log = { path = "../rtss_log" }
rtsa_dto = { path = "../rtsa_dto" }
rtsa_log = { path = "../rtsa_log" }

View File

@ -1,7 +1,7 @@
use std::vec;
use rtss_dto::common::DataType;
use rtss_log::tracing::debug;
use rtsa_dto::common::DataType;
use rtsa_log::tracing::debug;
use serde_json::Value;
use crate::{
@ -10,7 +10,7 @@ use crate::{
DbAccessError,
};
use super::RtssDbAccessor;
use super::RtsaDbAccessor;
/// 草稿数据管理
#[allow(async_fn_in_trait)]
@ -205,7 +205,7 @@ impl CreateDraftData {
}
}
impl DraftDataAccessor for RtssDbAccessor {
impl DraftDataAccessor for RtsaDbAccessor {
async fn paging_query_draft_data(
&self,
query: DraftDataQuery,
@ -436,8 +436,8 @@ mod tests {
use crate::{SyncUserInfo, UserAccessor};
use super::*;
use rtss_dto::common::{IscsStyle, Role};
use rtss_log::tracing::Level;
use rtsa_dto::common::{IscsStyle, Role};
use rtsa_log::tracing::Level;
use serde::{Deserialize, Serialize};
use sqlx::{types::chrono::Local, PgPool};
@ -449,8 +449,8 @@ mod tests {
// You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here.
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn basic_use_test(pool: PgPool) -> Result<(), DbAccessError> {
rtss_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = crate::db_access::RtssDbAccessor::new(pool);
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = crate::db_access::RtsaDbAccessor::new(pool);
// 同步10个用户
let mut users = vec![];
for i in 0..10 {
@ -458,7 +458,7 @@ mod tests {
id: i + 1,
name: format!("user{}", i + 1),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),

View File

@ -1,4 +1,4 @@
use rtss_dto::common::FeatureType;
use rtsa_dto::common::FeatureType;
use serde_json::Value;
use crate::{
@ -7,7 +7,7 @@ use crate::{
DbAccessError,
};
use super::RtssDbAccessor;
use super::RtsaDbAccessor;
/// 功能特性管理
#[allow(async_fn_in_trait)]
@ -34,7 +34,7 @@ pub trait FeatureAccessor {
async fn get_features(&self, ids: &[i32]) -> Result<Vec<FeatureModel>, DbAccessError>;
}
impl FeatureAccessor for RtssDbAccessor {
impl FeatureAccessor for RtsaDbAccessor {
async fn create_feature(&self, feature: &CreateFeature) -> Result<FeatureModel, DbAccessError> {
let table = FeatureColumn::Table.name();
let feature_type_column = FeatureColumn::FeatureType.name();
@ -217,8 +217,8 @@ impl FeaturePagingFilter {
#[cfg(test)]
mod tests {
use rtss_dto::common::Role;
use rtss_log::tracing::Level;
use rtsa_dto::common::Role;
use rtsa_log::tracing::Level;
use sqlx::{types::chrono::Local, PgPool};
use crate::{SyncUserInfo, UserAccessor};
@ -227,8 +227,8 @@ mod tests {
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn test_basic_use(pool: PgPool) -> Result<(), DbAccessError> {
rtss_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = crate::db_access::RtssDbAccessor::new(pool);
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = crate::db_access::RtsaDbAccessor::new(pool);
// 同步10个用户
let mut users = vec![];
for i in 0..10 {
@ -236,7 +236,7 @@ mod tests {
id: i + 1,
name: format!("user{}", i + 1),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),
@ -260,7 +260,7 @@ mod tests {
println!("create feature: {:?}", feature);
assert_eq!(feature.creator_id, creator_id);
assert_eq!(feature.updater_id, creator_id);
assert_eq!(feature.is_published, true);
assert!(feature.is_published);
assert_eq!(feature.config, create_config);
let feature_id = feature.id;
// 获取功能特性测试
@ -269,7 +269,7 @@ mod tests {
assert_eq!(feature.id, feature_id);
assert_eq!(feature.creator_id, creator_id);
assert_eq!(feature.updater_id, creator_id);
assert_eq!(feature.is_published, true);
assert!(feature.is_published);
assert_eq!(feature.config, create_config);
// 更新测试
let updater_id = 2;
@ -279,7 +279,7 @@ mod tests {
name: "test update".to_string(),
description: "test update".to_string(),
config: config.clone(),
updater_id: updater_id,
updater_id,
};
let feature = accessor.update_feature(&feature).await?;
println!("update feature: {:?}", feature);
@ -289,7 +289,7 @@ mod tests {
// 上下架测试
let feature = accessor.set_feature_published(feature_id, false).await?;
println!("set feature published: {:?}", feature);
assert_eq!(feature.is_published, false);
assert!(!feature.is_published);
// 分页查询测试
// 创建10个功能特性5个发布的5个未发布的
for i in 0..10 {

View File

@ -0,0 +1,82 @@
mod draft_data;
use std::sync::Mutex;
pub use draft_data::*;
mod release_data;
pub use release_data::*;
mod user;
use rtsa_log::tracing::error;
pub use user::*;
mod feature;
pub use feature::*;
use lazy_static::lazy_static;
use crate::{model::MqttClientIdSeq, DbAccessError};
lazy_static! {
static ref RDA: Mutex<Option<RtsaDbAccessor>> = Mutex::new(None);
}
/// 初始化全局默认数据库访问器
pub async fn init_default_db_accessor(url: &str) {
if RDA.lock().unwrap().is_some() {
error!("数据库访问器已初始化,请勿重复初始化");
return;
}
let accessor = get_db_accessor(url).await;
let mut rda = RDA.lock().unwrap();
*rda = Some(accessor);
}
/// 获取默认数据库访问器
pub fn get_default_db_accessor() -> RtsaDbAccessor {
let rda = RDA.lock().unwrap();
if rda.is_none() {
panic!("数据库访问器未初始化");
}
rda.as_ref().unwrap().clone()
}
#[derive(Clone)]
pub struct RtsaDbAccessor {
pool: sqlx::PgPool,
}
impl RtsaDbAccessor {
pub fn new(pool: sqlx::PgPool) -> Self {
RtsaDbAccessor { pool }
}
pub async fn get_next_mqtt_client_id(&self) -> Result<i64, DbAccessError> {
let seq_name = MqttClientIdSeq::Name.name();
let next = sqlx::query_scalar(&format!("SELECT nextval('{}')", seq_name))
.fetch_one(&self.pool)
.await?;
Ok(next)
}
}
pub async fn get_db_accessor(url: &str) -> RtsaDbAccessor {
let pool = sqlx::PgPool::connect(url).await.expect("连接数据库失败");
RtsaDbAccessor::new(pool)
}
#[cfg(test)]
mod tests {
use super::*;
use rtsa_log::tracing::{self, Level};
use sqlx::PgPool;
// You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here.
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn test_get_mqtt_client_id(pool: PgPool) -> Result<(), DbAccessError> {
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = crate::db_access::RtsaDbAccessor::new(pool);
for _ in 0..10 {
let id = accessor.get_next_mqtt_client_id().await?;
tracing::info!("id = {}", id);
assert!(id > 0);
}
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use rtss_dto::common::DataType;
use rtsa_dto::common::DataType;
use serde_json::Value;
use sqlx::{types::chrono, Postgres};
@ -11,7 +11,7 @@ use crate::{
DbAccessError,
};
use super::{CreateDraftData, DraftDataAccessor, RtssDbAccessor};
use super::{CreateDraftData, DraftDataAccessor, RtsaDbAccessor};
#[allow(async_fn_in_trait)]
pub trait ReleaseDataAccessor {
@ -109,7 +109,7 @@ pub struct ReleaseFromDraftResult {
pub struct ReleaseDataQuery {
pub name: Option<String>,
pub user_id: Option<i32>,
pub data_type: Option<rtss_dto::common::DataType>,
pub data_type: Option<rtsa_dto::common::DataType>,
pub options: Option<Value>,
pub is_published: Option<bool>,
}
@ -141,7 +141,7 @@ impl ReleaseDataQuery {
self
}
pub fn with_data_type(mut self, data_type: rtss_dto::common::DataType) -> Self {
pub fn with_data_type(mut self, data_type: rtsa_dto::common::DataType) -> Self {
self.data_type = Some(data_type);
self
}
@ -202,7 +202,7 @@ pub struct CreateReleaseVersionData {
pub user_id: i32,
}
impl RtssDbAccessor {
impl RtsaDbAccessor {
async fn insert_release_data_version<'e, 'c: 'e, E>(
&self,
data: CreateReleaseVersionData,
@ -237,7 +237,7 @@ impl RtssDbAccessor {
}
}
impl ReleaseDataAccessor for RtssDbAccessor {
impl ReleaseDataAccessor for RtsaDbAccessor {
async fn release_new_from_draft(
&self,
draft_id: i32,
@ -645,12 +645,12 @@ impl ReleaseDataAccessor for RtssDbAccessor {
#[cfg(test)]
mod tests {
use crate::{CreateDraftData, DraftDataAccessor, RtssDbAccessor, SyncUserInfo, UserAccessor};
use crate::{CreateDraftData, DraftDataAccessor, RtsaDbAccessor, SyncUserInfo, UserAccessor};
use super::*;
use chrono::Local;
use rtss_dto::common::{IscsStyle, Role};
use rtss_log::tracing::Level;
use rtsa_dto::common::{IscsStyle, Role};
use rtsa_log::tracing::Level;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
@ -695,8 +695,8 @@ mod tests {
// You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here.
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn test_basic_use(pool: PgPool) -> Result<(), DbAccessError> {
rtss_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = RtssDbAccessor::new(pool);
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = RtsaDbAccessor::new(pool);
// 同步10个用户
let mut users = vec![];
for i in 0..10 {
@ -704,7 +704,7 @@ mod tests {
id: i + 1,
name: format!("user{}", i + 1),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),
@ -717,7 +717,7 @@ mod tests {
let data = "test".as_bytes();
let draft = accessor
.create_draft_data(
CreateDraftData::new("test", rtss_dto::common::DataType::Iscs, 1)
CreateDraftData::new("test", rtsa_dto::common::DataType::Iscs, 1)
.with_options(
serde_json::to_value(IscsDataOptions {
style: IscsStyle::DaShiZhiNeng,
@ -745,7 +745,7 @@ mod tests {
// 发布新版本
let (release_data, version1) = accessor
.release_new_from_draft(draft.id, name, &description, None)
.release_new_from_draft(draft.id, name, description, None)
.await?;
assert_eq!(release_data.name, name);
// 检查使用版本是刚刚发布的版本
@ -759,7 +759,7 @@ mod tests {
// 检查版本描述
assert_eq!(version1.description, description);
// 检查上架状态
assert_eq!(release_data.is_published, true);
assert!(release_data.is_published);
// 检查草稿数据的默认发布数据id
let draft = accessor.query_draft_data_by_id(draft.id).await?;
assert_eq!(draft.default_release_data_id, Some(release_data.id));
@ -767,9 +767,9 @@ mod tests {
// name重复检查
let exist = accessor
.is_release_data_name_exist(rtss_dto::common::DataType::Iscs, name)
.is_release_data_name_exist(rtsa_dto::common::DataType::Iscs, name)
.await?;
assert_eq!(exist, true);
assert!(exist);
// 修改草稿数据
let data = "test2".as_bytes();
@ -798,7 +798,7 @@ mod tests {
let release_data = accessor
.set_release_data_published(release_data.id, false)
.await?;
assert_eq!(release_data.is_published, false);
assert!(!release_data.is_published);
assert!(release_data.updated_at > release_data.created_at);
println!("更新发布数据上架状态测试成功");
@ -849,7 +849,7 @@ mod tests {
let data = "test data".as_bytes();
let draft = accessor
.create_draft_data(
CreateDraftData::new(&name, rtss_dto::common::DataType::Em, i).with_data(data),
CreateDraftData::new(&name, rtsa_dto::common::DataType::Em, i).with_data(data),
)
.await?;
let (release_data, _) = accessor
@ -883,7 +883,7 @@ mod tests {
let page_result = accessor.paging_query_release_data_list(query, page).await?;
assert_eq!(page_result.total, 2);
// 分页查询发布数据,按数据类型过滤
let query = ReleaseDataQuery::new().with_data_type(rtss_dto::common::DataType::Em);
let query = ReleaseDataQuery::new().with_data_type(rtsa_dto::common::DataType::Em);
let page = PageQuery::new(1, 10);
let page_result = accessor.paging_query_release_data_list(query, page).await?;
assert_eq!(page_result.total, 8);

View File

@ -7,7 +7,7 @@ use crate::{
DbAccessError,
};
use super::RtssDbAccessor;
use super::RtsaDbAccessor;
/// 草稿数据管理
#[allow(async_fn_in_trait)]
@ -96,7 +96,7 @@ pub struct SyncUserInfo {
pub updated_at: Option<DateTime<Local>>,
}
impl RtssDbAccessor {
impl RtsaDbAccessor {
/// 首次同步用户数据
async fn sync_new_users(&self, users: &[SyncUserInfo]) -> Result<(), DbAccessError> {
let table = UserColumn::Table.name();
@ -223,7 +223,7 @@ impl RtssDbAccessor {
}
}
impl UserAccessor for RtssDbAccessor {
impl UserAccessor for RtsaDbAccessor {
async fn sync_user(&self, users: &[SyncUserInfo]) -> Result<(), DbAccessError> {
self.check_and_sync_user(users).await
}
@ -271,7 +271,7 @@ impl UserAccessor for RtssDbAccessor {
mod tests {
use std::time::Duration;
use rtss_log::tracing::Level;
use rtsa_log::tracing::Level;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
@ -293,14 +293,14 @@ mod tests {
#[sqlx::test(migrator = "crate::MIGRATOR")]
async fn test_sync_user(pool: PgPool) -> Result<(), DbAccessError> {
// 日志初始化
rtss_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = RtssDbAccessor::new(pool);
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let accessor = RtsaDbAccessor::new(pool);
let users = vec![
SyncUserInfo {
id: 1,
name: "test1".to_string(),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),
@ -310,7 +310,7 @@ mod tests {
id: 2,
name: "test2".to_string(),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::Admin]).unwrap(),
roles: serde_json::to_value(vec![Role::Admin]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),
@ -342,7 +342,7 @@ mod tests {
id: 1,
name: "test1".to_string(),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: Some("walker@163.com".to_string()),
mobile: None,
created_at: Local::now() - Duration::from_secs(60),
@ -352,7 +352,7 @@ mod tests {
id: 2,
name: "test2".to_string(),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::Admin, Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::Admin, Role::User]).unwrap(),
email: None,
mobile: Some("123456789".to_string()),
created_at: Local::now() - Duration::from_secs(60),
@ -362,7 +362,7 @@ mod tests {
id: 3,
name: "test3".to_string(),
password: "password".to_string(),
roles: serde_json::to_value(&vec![Role::User]).unwrap(),
roles: serde_json::to_value(vec![Role::User]).unwrap(),
email: None,
mobile: None,
created_at: Local::now(),

View File

@ -1,4 +1,4 @@
use rtss_dto::common::{DataType, FeatureType, Role};
use rtsa_dto::common::{DataType, FeatureType, Role};
use serde_json::Value;
use sqlx::types::{
chrono::{DateTime, Local},
@ -7,6 +7,18 @@ use sqlx::types::{
use crate::common::TableColumn;
pub enum MqttClientIdSeq {
Name,
}
impl MqttClientIdSeq {
pub fn name(&self) -> &str {
match self {
MqttClientIdSeq::Name => "rtsa.mqtt_client_id_seq",
}
}
}
#[derive(Debug)]
pub enum UserColumn {
Table,
@ -34,7 +46,7 @@ pub struct UserModel {
pub updated_at: DateTime<Local>,
}
/// 数据库表 rtss.draft_data 列映射
/// 数据库表 rtsa.draft_data 列映射
#[derive(Debug)]
pub enum DraftDataColumn {
Table,
@ -68,7 +80,7 @@ pub struct DraftDataModel {
pub updated_at: DateTime<Local>,
}
/// 数据库表 rtss.release_data 列映射
/// 数据库表 rtsa.release_data 列映射
#[derive(Debug)]
pub enum ReleaseDataColumn {
Table,
@ -99,9 +111,9 @@ pub struct ReleaseDataModel {
pub updated_at: DateTime<Local>,
}
/// 数据库表 rtss.release_data_version 列映射
/// 数据库表 rtsa.release_data_version 列映射
#[derive(Debug)]
pub(crate) enum ReleaseDataVersionColumn {
pub enum ReleaseDataVersionColumn {
Table,
Id,
ReleaseDataId,
@ -125,10 +137,10 @@ pub struct ReleaseDataVersionModel {
pub created_at: DateTime<Local>,
}
/// 数据库表 rtss.feature 列映射
/// 数据库表 rtsa.feature 列映射
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) enum FeatureColumn {
pub enum FeatureColumn {
Table,
Id,
FeatureType,
@ -157,10 +169,10 @@ pub struct FeatureModel {
pub updated_at: DateTime<Local>,
}
/// 数据库表 rtss.user_config 列映射
/// 数据库表 rtsa.user_config 列映射
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) enum UserConfigColumn {
pub enum UserConfigColumn {
Table,
Id,
UserId,
@ -183,7 +195,7 @@ pub struct UserConfigModel {
impl TableColumn for UserColumn {
fn name(&self) -> &str {
match self {
UserColumn::Table => "rtss.user",
UserColumn::Table => "rtsa.user",
UserColumn::Id => "id",
UserColumn::Username => "username",
UserColumn::Password => "password",
@ -199,7 +211,7 @@ impl TableColumn for UserColumn {
impl TableColumn for DraftDataColumn {
fn name(&self) -> &str {
match self {
DraftDataColumn::Table => "rtss.draft_data",
DraftDataColumn::Table => "rtsa.draft_data",
DraftDataColumn::Id => "id",
DraftDataColumn::Name => "name",
DraftDataColumn::DataType => "data_type",
@ -217,7 +229,7 @@ impl TableColumn for DraftDataColumn {
impl TableColumn for ReleaseDataColumn {
fn name(&self) -> &str {
match self {
ReleaseDataColumn::Table => "rtss.release_data",
ReleaseDataColumn::Table => "rtsa.release_data",
ReleaseDataColumn::Id => "id",
ReleaseDataColumn::Name => "name",
ReleaseDataColumn::DataType => "data_type",
@ -234,7 +246,7 @@ impl TableColumn for ReleaseDataColumn {
impl TableColumn for ReleaseDataVersionColumn {
fn name(&self) -> &str {
match self {
ReleaseDataVersionColumn::Table => "rtss.release_data_version",
ReleaseDataVersionColumn::Table => "rtsa.release_data_version",
ReleaseDataVersionColumn::Id => "id",
ReleaseDataVersionColumn::ReleaseDataId => "release_data_id",
ReleaseDataVersionColumn::Options => "options",
@ -249,7 +261,7 @@ impl TableColumn for ReleaseDataVersionColumn {
impl TableColumn for FeatureColumn {
fn name(&self) -> &str {
match self {
FeatureColumn::Table => "rtss.feature",
FeatureColumn::Table => "rtsa.feature",
FeatureColumn::Id => "id",
FeatureColumn::FeatureType => "feature_type",
FeatureColumn::Name => "name",
@ -267,7 +279,7 @@ impl TableColumn for FeatureColumn {
impl TableColumn for UserConfigColumn {
fn name(&self) -> &str {
match self {
UserConfigColumn::Table => "rtss.user_config",
UserConfigColumn::Table => "rtsa.user_config",
UserConfigColumn::Id => "id",
UserConfigColumn::UserId => "user_id",
UserConfigColumn::ConfigType => "config_type",

View File

@ -1,10 +1,11 @@
[package]
name = "rtss_dto"
name = "rtsa_dto"
version = "0.1.0"
edition = "2021"
[dependencies]
prost = "0.13"
prost-types = "0.13"
async-graphql = { version = "7.0.7", features = ["chrono"] }
sqlx = { workspace = true }
serde = { workspace = true }

View File

@ -16,7 +16,7 @@ fn main() {
{
std::env::set_var(
"PROTOC",
"../../rtss-proto-msg/protoc/protoc-27.4-win64/bin/protoc.exe",
"../../rtsa-proto-msg/protoc/protoc-27.4-win64/bin/protoc.exe",
);
}
Config::new()
@ -39,11 +39,12 @@ fn main() {
)
.compile_protos(
&[
"../../rtss-proto-msg/src/em_data.proto",
"../../rtss-proto-msg/src/common.proto",
"../../rtss-proto-msg/src/iscs_graphic_data.proto",
"../../rtsa-proto-msg/src/em_data.proto",
"../../rtsa-proto-msg/src/common.proto",
"../../rtsa-proto-msg/src/iscs_graphic_data.proto",
"../../rtsa-proto-msg/src/simulation.proto",
],
&["../../rtss-proto-msg/src/"],
&["../../rtsa-proto-msg/src/"],
)
.unwrap();
@ -51,5 +52,5 @@ fn main() {
Command::new("cargo")
.args(["fmt"])
.status()
.expect("Failed to run cargo fmt on rtss-dto");
.expect("Failed to run cargo fmt on rtsa-dto");
}

View File

@ -0,0 +1,27 @@
mod pb;
pub use pb::*;
#[cfg(test)]
mod tests {
use crate::simulation::{self, operation, SetSpeedParam};
#[test]
fn test_prost_any() {
let op = simulation::Operation {
otype: simulation::OperationType::SetSpeed as i32,
param: Some(operation::Param::SetSpeedParam(SetSpeedParam {
speed: 1.0,
})),
};
println!("{:?}", op);
if let Some(param) = op.param {
match param {
operation::Param::SetSpeedParam(ssp) => {
assert_eq!(ssp.speed, 1.0);
}
}
}
}
}

View File

@ -0,0 +1,662 @@
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IscsGraphicStorage {
#[prost(message, repeated, tag = "1")]
pub cctv_of_equipment_layout_storages: ::prost::alloc::vec::Vec<
CctvOfEquipmentLayoutStorage,
>,
#[prost(message, repeated, tag = "2")]
pub fas_of_platform_alarm_storages: ::prost::alloc::vec::Vec<
FasOfPlatformAlarmStorage,
>,
#[prost(message, repeated, tag = "3")]
pub bas_of_escalator_storages: ::prost::alloc::vec::Vec<BasOfEscalatorStorage>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UniqueIdOfStationLayout {
/// 城市
#[prost(string, tag = "1")]
pub city: ::prost::alloc::string::String,
/// 线路号
#[prost(string, tag = "2")]
pub line_id: ::prost::alloc::string::String,
/// 地图的公里标主坐标系
#[prost(string, tag = "3")]
pub main_coordinate_system: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CommonGraphicStorage {
#[prost(message, repeated, tag = "1")]
pub arrows: ::prost::alloc::vec::Vec<Arrow>,
#[prost(message, repeated, tag = "2")]
pub texts: ::prost::alloc::vec::Vec<Text>,
#[prost(message, repeated, tag = "3")]
pub rects: ::prost::alloc::vec::Vec<Rect>,
#[prost(message, repeated, tag = "4")]
pub lines: ::prost::alloc::vec::Vec<Line>,
#[prost(message, repeated, tag = "5")]
pub circles: ::prost::alloc::vec::Vec<Circle>,
#[prost(message, repeated, tag = "6")]
pub buttons: ::prost::alloc::vec::Vec<Button>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Arrow {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
pub points: ::prost::alloc::vec::Vec<super::common::Point>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Text {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub content: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub color: ::prost::alloc::string::String,
#[prost(int32, tag = "5")]
pub font_size: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Rect {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
/// 线宽
#[prost(int32, tag = "3")]
pub line_width: i32,
/// 线色
#[prost(string, tag = "4")]
pub line_color: ::prost::alloc::string::String,
/// 宽度
#[prost(float, tag = "5")]
pub width: f32,
/// 高度
#[prost(float, tag = "6")]
pub height: f32,
/// 圆角半径
#[prost(int32, tag = "7")]
pub radius: i32,
/// 画第一个点的坐标
#[prost(message, optional, tag = "8")]
pub point: ::core::option::Option<super::common::Point>,
/// 填充色
#[prost(string, tag = "9")]
pub fill_color: ::prost::alloc::string::String,
/// 透明度
#[prost(float, tag = "10")]
pub alpha: f32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Line {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
/// 编号
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
/// 点列表
#[prost(message, repeated, tag = "3")]
pub points: ::prost::alloc::vec::Vec<super::common::Point>,
/// 是否曲线
#[prost(bool, tag = "4")]
pub is_curve: bool,
/// 曲线分段数
#[prost(int32, tag = "5")]
pub segments_count: i32,
/// 线宽
#[prost(int32, tag = "6")]
pub line_width: i32,
/// 线色
#[prost(string, tag = "7")]
pub line_color: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Circle {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(message, optional, tag = "3")]
pub position: ::core::option::Option<super::common::Point>,
#[prost(float, tag = "4")]
pub radius: f32,
#[prost(int32, tag = "5")]
pub line_width: i32,
#[prost(string, tag = "6")]
pub line_color: ::prost::alloc::string::String,
#[prost(string, tag = "7")]
pub fill_color: ::prost::alloc::string::String,
#[prost(float, tag = "8")]
pub alpha: f32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Button {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub code_color: ::prost::alloc::string::String,
#[prost(int32, tag = "4")]
pub code_font_size: i32,
/// 所属ISCS子菜单
#[prost(string, tag = "5")]
pub belong_sub_menu: ::prost::alloc::string::String,
/// 类似icon
#[prost(enumeration = "button::ButtonType", tag = "6")]
pub button_type: i32,
/// 宽度
#[prost(float, tag = "7")]
pub width: f32,
/// 高度
#[prost(float, tag = "8")]
pub height: f32,
/// 圆角半径_可变圆
#[prost(int32, tag = "9")]
pub radius: i32,
/// 填充色
#[prost(string, tag = "10")]
pub fill_color: ::prost::alloc::string::String,
/// 透明度
#[prost(float, tag = "11")]
pub alpha: f32,
}
/// Nested message and enum types in `Button`.
pub mod button {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum ButtonType {
/// 没有Icon
NoIcon = 0,
/// 监控方形
CctvRect = 1,
/// 监控样子的按钮
CctvMonitor = 2,
/// 半圆样子的按钮
CctvSemicircle = 3,
}
impl ButtonType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ButtonType::NoIcon => "noIcon",
ButtonType::CctvRect => "cctvRect",
ButtonType::CctvMonitor => "cctvMonitor",
ButtonType::CctvSemicircle => "cctvSemicircle",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"noIcon" => Some(Self::NoIcon),
"cctvRect" => Some(Self::CctvRect),
"cctvMonitor" => Some(Self::CctvMonitor),
"cctvSemicircle" => Some(Self::CctvSemicircle),
_ => None,
}
}
}
}
/// 手动报警按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ManualAlarmButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 消防栓报警按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HydrantAlarmButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 气体灭火
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GasExtinguishing {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 烟雾探测器
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SmokeDetector {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 温度探测器
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TemperatureDetector {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BasOfEscalatorStorage {
#[prost(string, tag = "1")]
pub station_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub canvas: ::core::option::Option<super::common::Canvas>,
#[prost(message, optional, tag = "3")]
pub common_graphic_storage: ::core::option::Option<CommonGraphicStorage>,
#[prost(message, repeated, tag = "4")]
pub escalators: ::prost::alloc::vec::Vec<Escalator>,
#[prost(message, repeated, tag = "5")]
pub vertical_elevators: ::prost::alloc::vec::Vec<VerticalElevator>,
}
/// 自动扶梯
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Escalator {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 垂直电梯
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct VerticalElevator {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CctvOfEquipmentLayoutStorage {
#[prost(string, tag = "1")]
pub station_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub canvas: ::core::option::Option<super::common::Canvas>,
#[prost(message, optional, tag = "3")]
pub common_graphic_storage: ::core::option::Option<CommonGraphicStorage>,
/// 分层
#[prost(enumeration = "cctv_of_equipment_layout_storage::LayerType", tag = "4")]
pub layer: i32,
}
/// Nested message and enum types in `CCTVOfEquipmentLayoutStorage`.
pub mod cctv_of_equipment_layout_storage {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum LayerType {
/// 站厅
StationHall = 0,
/// 站台
Platform = 1,
/// 云台
Ptz = 2,
}
impl LayerType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
LayerType::StationHall => "StationHall",
LayerType::Platform => "platform",
LayerType::Ptz => "PTZ",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"StationHall" => Some(Self::StationHall),
"platform" => Some(Self::Platform),
"PTZ" => Some(Self::Ptz),
_ => None,
}
}
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FasOfPlatformAlarmStorage {
#[prost(string, tag = "1")]
pub station_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub canvas: ::core::option::Option<super::common::Canvas>,
#[prost(message, optional, tag = "3")]
pub common_graphic_storage: ::core::option::Option<CommonGraphicStorage>,
/// 分区
#[prost(string, tag = "4")]
pub partition: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "5")]
pub fas_failure_control_hosts: ::prost::alloc::vec::Vec<FasFailureControlHost>,
#[prost(message, repeated, tag = "6")]
pub fas_alarms: ::prost::alloc::vec::Vec<FasAlarm>,
#[prost(message, repeated, tag = "7")]
pub manual_alarm_buttons: ::prost::alloc::vec::Vec<ManualAlarmButton>,
#[prost(message, repeated, tag = "8")]
pub hydrant_alarm_buttons: ::prost::alloc::vec::Vec<HydrantAlarmButton>,
#[prost(message, repeated, tag = "9")]
pub gas_extinguishings: ::prost::alloc::vec::Vec<GasExtinguishing>,
#[prost(message, repeated, tag = "10")]
pub smoke_detectors: ::prost::alloc::vec::Vec<SmokeDetector>,
#[prost(message, repeated, tag = "11")]
pub temperature_detectors: ::prost::alloc::vec::Vec<TemperatureDetector>,
#[prost(message, repeated, tag = "12")]
pub fire_shutters: ::prost::alloc::vec::Vec<FireShutter>,
#[prost(message, repeated, tag = "13")]
pub fire_pumps: ::prost::alloc::vec::Vec<FirePump>,
#[prost(message, repeated, tag = "14")]
pub spray_pumps: ::prost::alloc::vec::Vec<SprayPump>,
#[prost(message, repeated, tag = "15")]
pub stabilized_pressure_pumps: ::prost::alloc::vec::Vec<StabilizedPressurePump>,
#[prost(message, repeated, tag = "16")]
pub acs: ::prost::alloc::vec::Vec<Acs>,
#[prost(message, repeated, tag = "17")]
pub afc: ::prost::alloc::vec::Vec<Afc>,
#[prost(message, repeated, tag = "18")]
pub non_fire_power_supplies: ::prost::alloc::vec::Vec<NonFirePowerSupply>,
#[prost(message, repeated, tag = "19")]
pub water_flow_indicators: ::prost::alloc::vec::Vec<WaterFlowIndicator>,
#[prost(message, repeated, tag = "20")]
pub signal_butterfly_valves: ::prost::alloc::vec::Vec<SignalButterflyValve>,
#[prost(message, repeated, tag = "21")]
pub pressure_switches: ::prost::alloc::vec::Vec<PressureSwitch>,
#[prost(message, repeated, tag = "22")]
pub fault_valves: ::prost::alloc::vec::Vec<FaultValve>,
#[prost(message, repeated, tag = "23")]
pub start_pump_buttons: ::prost::alloc::vec::Vec<StartPumpButton>,
#[prost(message, repeated, tag = "24")]
pub temperature_cables: ::prost::alloc::vec::Vec<TemperatureCable>,
#[prost(message, repeated, tag = "25")]
pub emergency_lightings: ::prost::alloc::vec::Vec<EmergencyLighting>,
#[prost(message, repeated, tag = "26")]
pub elevator_lift_to_tops: ::prost::alloc::vec::Vec<ElevatorLiftToTop>,
#[prost(message, repeated, tag = "27")]
pub electric_butterfly_valves: ::prost::alloc::vec::Vec<ElectricButterflyValve>,
#[prost(message, repeated, tag = "28")]
pub fire_valves: ::prost::alloc::vec::Vec<FireValve>,
#[prost(message, repeated, tag = "29")]
pub electric_fire_extinguishing_valves: ::prost::alloc::vec::Vec<
ElectricFireExtinguishingValve,
>,
#[prost(message, repeated, tag = "30")]
pub fire_intercommunication_signals: ::prost::alloc::vec::Vec<
FireIntercommunicationSignal,
>,
}
/// 火灾故障控制主机
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FasFailureControlHost {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 警铃
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FasAlarm {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 防火卷帘
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FireShutter {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(enumeration = "fire_shutter::ShutterType", tag = "3")]
pub r#type: i32,
}
/// Nested message and enum types in `FireShutter`.
pub mod fire_shutter {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum ShutterType {
/// 隔断型
Partition = 0,
/// 疏散型
Dispersal = 1,
}
impl ShutterType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ShutterType::Partition => "partition",
ShutterType::Dispersal => "dispersal",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"partition" => Some(Self::Partition),
"dispersal" => Some(Self::Dispersal),
_ => None,
}
}
}
}
/// 消防泵
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FirePump {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 喷淋泵
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SprayPump {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 稳压泵
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StabilizedPressurePump {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// ACS
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Acs {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// AFC
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Afc {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 非消防电源
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NonFirePowerSupply {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 水流指示器
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WaterFlowIndicator {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 信号蝶阀
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SignalButterflyValve {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 压力开关
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PressureSwitch {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 故障阀
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FaultValve {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 启泵按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StartPumpButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 感温电缆
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TemperatureCable {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 应急照明
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EmergencyLighting {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 电梯归首
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ElevatorLiftToTop {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 电动蝶阀
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ElectricButterflyValve {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 防火阀
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FireValve {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 电动防烟防火阀
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ElectricFireExtinguishingValve {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 火灾互联互通信号
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FireIntercommunicationSignal {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}

View File

@ -1,2 +1,3 @@
pub mod common;
pub mod em_data;
pub mod simulation;

View File

@ -0,0 +1,77 @@
// This file is @generated by prost-build.
/// 仿真操作
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct Operation {
/// 操作类型
#[prost(enumeration = "OperationType", tag = "1")]
pub otype: i32,
/// 操作参数
#[prost(oneof = "operation::Param", tags = "2")]
pub param: ::core::option::Option<operation::Param>,
}
/// Nested message and enum types in `Operation`.
pub mod operation {
/// 操作参数
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Oneof)]
pub enum Param {
/// 设置仿真运行速度参数
#[prost(message, tag = "2")]
SetSpeedParam(super::SetSpeedParam),
}
}
/// 设置仿真运行速度参数
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct SetSpeedParam {
/// 运行速度
#[prost(float, tag = "1")]
pub speed: f32,
}
/// 仿真操作类型
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum OperationType {
/// 未知
Unknown = 0,
/// -------仿真控制操作--------
/// 暂停
Pause = 1,
/// 恢复运行
Unpause = 2,
/// 重置
Reset = 3,
/// 设置运行速度
SetSpeed = 4,
/// 销毁
Destroy = 5,
}
impl OperationType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
OperationType::Unknown => "Unknown",
OperationType::Pause => "Pause",
OperationType::Unpause => "Unpause",
OperationType::Reset => "Reset",
OperationType::SetSpeed => "SetSpeed",
OperationType::Destroy => "Destroy",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"Unknown" => Some(Self::Unknown),
"Pause" => Some(Self::Pause),
"Unpause" => Some(Self::Unpause),
"Reset" => Some(Self::Reset),
"SetSpeed" => Some(Self::SetSpeed),
"Destroy" => Some(Self::Destroy),
_ => None,
}
}
}

View File

@ -1,5 +1,5 @@
[package]
name = "rtss_log"
name = "rtsa_log"
version = "0.1.0"
edition = "2021"

View File

@ -0,0 +1,15 @@
[package]
name = "rtsa_mqtt"
version = "0.1.0"
edition = "2021"
[dependencies]
rumqttc = { version = "0.24.0", features = ["url"] }
tokio = { workspace = true, features = ["sync"] }
async-trait = { workspace = true }
bytes = { workspace = true }
lazy_static = { workspace = true }
thiserror = { workspace = true }
tower = { workspace = true }
rtsa_log = { path = "../rtsa_log" }

View File

@ -0,0 +1,16 @@
use rumqttc::v5::ClientError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MqttClientError {
#[error("未知的Mqtt客户端错误")]
Unknown,
#[error("客户端已设置")]
AlreadySet,
#[error("rumqttc 错误: {0}")]
ClientError(#[from] ClientError),
#[error("全局客户端未设置")]
NoClient,
#[error("请求超时")]
RequestTimeout,
}

512
crates/rtsa_mqtt/src/lib.rs Normal file
View File

@ -0,0 +1,512 @@
use core::panic;
use std::{
sync::{
atomic::{AtomicU64, Ordering},
Arc, Mutex,
},
task::Waker,
time::Duration,
};
use bytes::Bytes;
use lazy_static::lazy_static;
use rtsa_log::tracing::{error, info, trace};
use rumqttc::{
v5::{
mqttbytes::{
v5::{Packet, Publish, PublishProperties},
QoS,
},
AsyncClient, Event, EventLoop, MqttOptions,
},
Outgoing,
};
use service::{Handler, MqttRequest, MqttRouter};
use tokio::{sync::oneshot, time::timeout};
mod error;
use error::MqttClientError;
mod service;
lazy_static! {
/// 全局静态MqttClient实例
/// 使用注意事项:
/// 每次订阅/发布/请求时都通过get_global_mqtt_client获取新的实例否则可能会出现死锁
static ref MQTT_CLIENT: tokio::sync::Mutex<Option<MqttClient>> = tokio::sync::Mutex::new(None);
}
/// 初始化全局MqttClient实例
pub async fn init_global_mqtt_client(
mut options: MqttClientOptions,
) -> Result<(), MqttClientError> {
let client = options.build();
set_global_mqtt_client(client).await
}
/// 设置全局MqttClient实例
pub async fn set_global_mqtt_client(client: MqttClient) -> Result<(), MqttClientError> {
let mut mqtt_client = MQTT_CLIENT.lock().await;
if mqtt_client.is_some() {
return Err(MqttClientError::AlreadySet);
}
*mqtt_client = Some(client);
Ok(())
}
/// 获取全局MqttClient实例
pub async fn get_global_mqtt_client() -> MqttClient {
let mqtt_client = MQTT_CLIENT.lock().await;
if let Some(client) = mqtt_client.as_ref() {
return client.clone();
}
panic!("MqttClient未初始化: 使用init_global_mqtt_client初始化或者在main函数中调用set_global_mqtt_client设置");
}
pub struct MqttClientOptions {
id: String,
options: MqttOptions,
/// mqtt客户端请求队列的最大容量
max_cap: usize,
request_timeout: Duration,
}
impl MqttClientOptions {
pub fn new(id: &str, url: &str) -> Self {
Self {
id: id.to_string(),
options: MqttOptions::parse_url(format!("{}?client_id={}", url, id))
.expect("解析mqtt url失败"),
max_cap: 30,
request_timeout: Duration::from_secs(5),
}
}
pub fn set_max_cap(mut self, max_cap: usize) -> Self {
self.max_cap = max_cap;
self
}
pub fn set_request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
pub fn set_credentials(mut self, username: &str, password: &str) -> Self {
self.options.set_credentials(username, password);
self
}
pub fn build(&mut self) -> MqttClient {
self.options.set_keep_alive(Duration::from_secs(10));
let (client, eventloop) = AsyncClient::new(self.options.clone(), self.max_cap);
let cli = MqttClient {
id: self.id.clone(),
request_timeout: self.request_timeout,
client,
request_id: Arc::new(AtomicU64::new(0)),
router: MqttRouter::new(),
};
cli.run_async(eventloop);
cli
}
}
/// MQTT客户端
/// id: 客户端ID,从数据库的id序列中获取
/// 客户端具有的功能:
/// 1. 启动
/// 2. 订阅
/// 3. 发布
/// 4. 实现类似http的请求相应功能
/// 5. 断开连接
#[derive(Clone)]
pub struct MqttClient {
id: String,
/// 全局的请求超时时间
request_timeout: Duration,
client: AsyncClient,
request_id: Arc<AtomicU64>,
router: MqttRouter,
}
impl MqttClient {
pub async fn clear(&self) -> Result<(), MqttClientError> {
self.client.disconnect().await?;
// 清空订阅处理器
self.router.clear();
Ok(())
}
pub fn id(&self) -> &str {
&self.id
}
pub async fn publish(
&self,
topic: &str,
qos: QoS,
payload: Vec<u8>,
) -> Result<(), MqttClientError> {
self.client.publish(topic, qos, false, payload).await?;
Ok(())
}
pub async fn add_route<H>(&self, topic: &str, handler: H, qos: QoS)
where
H: Handler + Send + Sync + 'static,
{
self.client.subscribe(topic, qos).await.unwrap();
self.router.add_route(topic, handler);
}
pub async fn remove_route(&self, topic: &str) {
self.client.unsubscribe(topic).await.unwrap();
self.router.remove_route(topic);
}
pub fn next_request_id(&self) -> u64 {
self.request_id.fetch_add(1, Ordering::Relaxed)
}
pub async fn handle_request(
&self,
req: Request,
timeout: Duration,
) -> Result<Response, MqttClientError> {
// 订阅响应主题
let response_topic = format!("{}/{}/resp/{}", self.id, req.topic, self.next_request_id());
// 创建请求future
let response_future = MqttResponseFuture::new(&response_topic, timeout);
// 添加响应处理器
self.add_route(&response_topic, response_future.clone(), QoS::ExactlyOnce)
.await;
// 发布请求
let property = PublishProperties {
response_topic: Some(response_topic.clone()),
..req.properties.unwrap_or_default()
};
self.client
.publish_with_properties(req.topic, req.qos, false, req.payload, property)
.await?;
// 等待响应
let resp = response_future.await;
// 注销响应处理器并取消订阅
self.remove_route(&response_topic).await;
if resp.is_timeout() {
return Err(MqttClientError::RequestTimeout);
}
Ok(resp)
}
/// 发送请求并等待响应
pub async fn request(&self, req: Request) -> Result<Response, MqttClientError> {
self.handle_request(req, self.request_timeout).await
}
/// 发送请求并等待响应,指定响应超时时间
/// 响应超时时间为0时表示永不超时
pub async fn request_with_timeout(
&self,
req: Request,
timeout: Duration,
) -> Result<Response, MqttClientError> {
self.handle_request(req, timeout).await
}
fn run_async(&self, eventloop: EventLoop) {
let cli = self.clone();
tokio::spawn(async move {
cli.run(eventloop).await;
});
}
async fn run(&self, mut eventloop: EventLoop) {
while let Ok(notification) = eventloop.poll().await {
match notification {
Event::Incoming(Packet::Publish(publish)) => {
trace!("Received message: {:?}", publish);
let this = self.clone();
let router = self.router.clone();
tokio::spawn(async move {
let response_topic = publish
.properties
.clone()
.and_then(|p| p.response_topic.clone());
if let Some(resp) = router.handle_request(MqttRequest::new(publish)).await {
if let Some(r_topic) = response_topic {
this.publish(&r_topic, QoS::AtMostOnce, resp.payload.to_vec())
.await
.unwrap();
}
}
});
}
Event::Outgoing(Outgoing::Disconnect) => {
info!("Disconnected to the broker");
break;
}
Event::Incoming(Packet::Disconnect(disconnect)) => {
info!("Disconnected from the broker: {:?}", disconnect);
break;
}
_ => {
trace!("Unhandled event: {:?}", notification);
}
}
}
}
}
pub struct Request {
topic: String,
qos: QoS,
payload: Bytes,
properties: Option<PublishProperties>,
}
impl Request {
pub fn new(topic: &str, payload: Bytes) -> Self {
Self {
topic: topic.to_string(),
qos: QoS::AtMostOnce,
payload,
properties: None,
}
}
pub fn with_qos(mut self, qos: QoS) -> Self {
self.qos = qos;
self
}
pub fn with_properties(mut self, properties: PublishProperties) -> Self {
self.properties = Some(properties);
self
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum MqttResponseState {
Waiting,
Received,
Timeout,
}
/// MQTT请求响应
#[derive(Clone, Debug)]
pub struct Response {
state: Arc<Mutex<MqttResponseState>>,
response: Arc<Mutex<Publish>>,
}
impl Default for Response {
fn default() -> Self {
Self::new()
}
}
impl Response {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(MqttResponseState::Waiting)),
response: Arc::new(Mutex::new(Publish::default())),
}
}
pub fn is_waiting(&self) -> bool {
*self.state.lock().unwrap() == MqttResponseState::Waiting
}
pub fn is_received(&self) -> bool {
*self.state.lock().unwrap() == MqttResponseState::Received
}
pub fn is_timeout(&self) -> bool {
*self.state.lock().unwrap() == MqttResponseState::Timeout
}
pub fn set_timeout(&self) {
*self.state.lock().unwrap() = MqttResponseState::Timeout;
}
pub fn set(&self, response: Publish) {
*self.state.lock().unwrap() = MqttResponseState::Received;
*self.response.lock().unwrap() = response;
}
pub fn get(&self) -> Publish {
self.response.lock().unwrap().clone()
}
}
/// MQTT响应Future
#[derive(Clone)]
pub struct MqttResponseFuture {
pub start_time: std::time::Instant,
timeout: Duration,
tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
waker: Arc<Mutex<Option<Waker>>>,
response_topic: String,
response: Response,
}
impl MqttResponseFuture {
pub fn new(response_topic: &str, timeout: Duration) -> Self {
let (tx, rx) = oneshot::channel();
let r = Self {
start_time: std::time::Instant::now(),
timeout,
tx: Arc::new(Mutex::new(Some(tx))),
waker: Arc::new(Mutex::new(None)),
response_topic: response_topic.to_string(),
response: Response::new(),
};
// 启动超时检查
r.start_timeout_monitor(rx);
r
}
/// 启动超时监控任务逻辑
fn start_timeout_monitor(&self, rx: oneshot::Receiver<()>) {
if self.timeout.as_millis() == 0 {
return;
}
let response = self.response.clone();
let response_topic = self.response_topic.clone();
let duration = self.timeout;
let waker = self.waker.clone();
tokio::spawn(async move {
if (timeout(duration, rx).await).is_err() {
error!("Mqtt response timeout: {:?}", response_topic);
response.set_timeout();
if let Some(waker) = waker.lock().unwrap().take() {
waker.wake();
}
}
});
}
}
impl Handler for MqttResponseFuture {
fn handle(
&self,
req: MqttRequest,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Option<service::MqttResponse>> + Send>>
{
let topic = req.topic();
trace!("MqttResponseFuture handle: {:?}", topic);
if topic == self.response_topic {
self.response.set(req.get());
if let Some(tx) = self.tx.lock().unwrap().take() {
tx.send(())
.expect("Send Mqtt response timeout signal failed");
}
if let Some(waker) = self.waker.lock().unwrap().take() {
waker.wake();
}
}
Box::pin(async { None })
}
}
impl std::future::Future for MqttResponseFuture {
type Output = Response;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
if self.response.is_waiting() {
trace!(
"topic={} Response future poll waiting...",
self.response_topic
);
self.waker.lock().unwrap().replace(cx.waker().clone());
std::task::Poll::Pending
} else {
trace!(
"topic={} Response future poll ready: {:?}",
self.response_topic,
self.response
);
std::task::Poll::Ready(self.response.clone())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rtsa_log::tracing::Level;
use tokio::time::Duration;
fn create_mqtt_options() -> MqttClientOptions {
MqttClientOptions::new("rtsa_test1", "tcp://localhost:1883")
.set_credentials("rtsa", "Joylink@0503")
}
#[tokio::test]
async fn test_mqtt_client_initialization() {
let options = create_mqtt_options();
let result = init_global_mqtt_client(options).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mqtt_client_publish() {
let options = create_mqtt_options();
init_global_mqtt_client(options).await.unwrap();
let mqtt_client = get_global_mqtt_client().await;
let result = mqtt_client
.publish("test/topic", QoS::AtLeastOnce, b"Hello, MQTT!".to_vec())
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mqtt_client_request_response() {
rtsa_log::Logging::default().with_level(Level::TRACE).init();
let options = create_mqtt_options();
init_global_mqtt_client(options).await.unwrap();
let mqtt_client = get_global_mqtt_client().await;
struct EchoHandler;
impl Handler for EchoHandler {
fn handle(
&self,
req: MqttRequest,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Option<service::MqttResponse>> + Send>,
> {
let payload = req.payload();
Box::pin(async move { Some(service::MqttResponse::new(payload)) })
}
}
mqtt_client
.add_route("test/echo", EchoHandler, QoS::AtLeastOnce)
.await;
let request = Request::new("test/echo", Bytes::from("Echo message"));
let response = mqtt_client.request(request).await.unwrap();
assert_eq!(response.get().payload, Bytes::from("Echo message"));
mqtt_client.remove_route("test/echo").await;
}
#[tokio::test]
async fn test_mqtt_client_timeout() {
let options = create_mqtt_options();
init_global_mqtt_client(options).await.unwrap();
let mqtt_client = get_global_mqtt_client().await;
let request =
Request::new("test/timeout", Bytes::from("Timeout test")).with_qos(QoS::ExactlyOnce);
let result = mqtt_client
.request_with_timeout(request, Duration::from_secs(1))
.await;
assert!(result.is_err());
}
}

View File

@ -0,0 +1,247 @@
use std::{
collections::HashMap,
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use bytes::Bytes;
use rumqttc::v5::mqttbytes::v5::{Publish, PublishProperties};
pub struct MqttRequest {
publish: Publish,
}
impl MqttRequest {
pub fn new(publish: Publish) -> Self {
MqttRequest { publish }
}
pub fn topic(&self) -> String {
String::from_utf8_lossy(&self.publish.topic).to_string()
}
pub fn payload(&self) -> Bytes {
self.publish.payload.clone()
}
pub fn get(&self) -> Publish {
self.publish.clone()
}
}
pub struct MqttResponse {
pub properties: Option<PublishProperties>,
pub payload: Bytes,
}
impl MqttResponse {
pub fn new(payload: Bytes) -> Self {
MqttResponse {
properties: None,
payload,
}
}
pub fn with_properties(payload: Bytes, properties: PublishProperties) -> Self {
MqttResponse {
properties: Some(properties),
payload,
}
}
}
pub trait Handler: Send + Sync + 'static {
fn handle(
&self,
req: MqttRequest,
) -> Pin<Box<dyn Future<Output = Option<MqttResponse>> + Send>>;
}
impl<F, Fut> Handler for F
where
F: Fn(MqttRequest) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Option<MqttResponse>> + Send + 'static,
{
fn handle(
&self,
req: MqttRequest,
) -> Pin<Box<dyn Future<Output = Option<MqttResponse>> + Send>> {
Box::pin((self)(req))
}
}
#[derive(Clone)]
pub struct MqttRouter {
routes: Arc<Mutex<HashMap<String, Arc<dyn Handler + Send + Sync>>>>,
}
impl MqttRouter {
pub fn new() -> Self {
MqttRouter {
routes: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn add_route<H>(&self, topic: &str, handler: H)
where
H: Handler + Send + Sync + 'static,
{
self.routes
.lock()
.unwrap()
.insert(topic.to_string(), Arc::new(handler));
}
pub fn remove_route(&self, topic: &str) {
self.routes.lock().unwrap().remove(topic);
}
fn get_handler(&self, topic: &str) -> Option<Arc<dyn Handler + Send + Sync>> {
let routes = self.routes.lock().unwrap();
routes.get(topic).cloned()
}
pub fn clear(&self) {
self.routes.lock().unwrap().clear();
}
pub async fn handle_request(&self, req: MqttRequest) -> Option<MqttResponse> {
if let Some(handler) = self.get_handler(&req.topic()) {
handler.handle(req).await
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use rumqttc::v5::mqttbytes::v5::Publish;
// Helper function to create a Publish message
fn create_publish(topic: &str, payload: &[u8]) -> Publish {
Publish {
topic: topic.as_bytes().to_vec().into(),
payload: Bytes::from(payload.to_vec()),
dup: false,
qos: rumqttc::v5::mqttbytes::QoS::AtMostOnce,
retain: false,
pkid: 0,
properties: None,
}
}
// Sample handler that echoes the payload back
async fn echo_handler(req: MqttRequest) -> Option<MqttResponse> {
Some(MqttResponse {
properties: None,
payload: req.payload(),
})
}
// Sample handler that returns None
async fn none_handler(_req: MqttRequest) -> Option<MqttResponse> {
None
}
#[tokio::test]
async fn test_add_and_get_handler() {
let router = MqttRouter::new();
router.add_route("test/topic", echo_handler);
assert!(router.get_handler("test/topic").is_some());
assert!(router.get_handler("invalid/topic").is_none());
}
#[tokio::test]
async fn test_handle_request_with_existing_route() {
let router = MqttRouter::new();
router.add_route("test/topic", echo_handler);
let publish = create_publish("test/topic", b"hello");
let req = MqttRequest::new(publish);
let response = router.handle_request(req).await;
assert!(response.is_some());
assert_eq!(response.unwrap().payload, Bytes::from("hello"));
}
#[tokio::test]
async fn test_handle_request_with_nonexistent_route() {
let router = MqttRouter::new();
let publish = create_publish("invalid/topic", b"hello");
let req = MqttRequest::new(publish);
let response = router.handle_request(req).await;
assert!(response.is_none());
}
#[tokio::test]
async fn test_handler_returning_none() {
let router = MqttRouter::new();
router.add_route("test/topic", none_handler);
let publish = create_publish("test/topic", b"hello");
let req = MqttRequest::new(publish);
let response = router.handle_request(req).await;
assert!(response.is_none());
}
#[tokio::test]
async fn test_multiple_routes() {
let router = MqttRouter::new();
router.add_route("topic/one", echo_handler);
router.add_route("topic/two", none_handler);
let publish_one = create_publish("topic/one", b"payload1");
let req_one = MqttRequest::new(publish_one);
let response_one = router.handle_request(req_one).await;
assert!(response_one.is_some());
assert_eq!(response_one.unwrap().payload, Bytes::from("payload1"));
let publish_two = create_publish("topic/two", b"payload2");
let req_two = MqttRequest::new(publish_two);
let response_two = router.handle_request(req_two).await;
assert!(response_two.is_none());
}
#[tokio::test]
async fn test_concurrent_access() {
use std::sync::Arc;
use tokio::sync::Barrier;
let router = Arc::new(MqttRouter::new());
router.add_route("test/topic", echo_handler);
let barrier = Arc::new(Barrier::new(10));
let mut handles = Vec::new();
for _ in 0..10 {
let router_cloned = router.clone();
let barrier_cloned = barrier.clone();
let handle = tokio::spawn(async move {
let publish = create_publish("test/topic", b"concurrent");
let req = MqttRequest::new(publish);
barrier_cloned.wait().await;
let response = router_cloned.handle_request(req).await;
assert!(response.is_some());
assert_eq!(response.unwrap().payload, Bytes::from("concurrent"));
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
}
}

View File

@ -1,136 +0,0 @@
use async_graphql::{
dataloader::DataLoader, ComplexObject, Context, InputObject, Object, SimpleObject,
};
use chrono::NaiveDateTime;
use rtss_db::{FeatureAccessor, RtssDbAccessor};
use rtss_dto::common::FeatureType;
use serde_json::Value;
use crate::{
apis::{PageDto, PageQueryDto},
loader::RtssDbLoader,
};
use super::user::UserId;
#[derive(Default)]
pub struct FeatureQuery;
#[derive(Default)]
pub struct FeatureMutation;
#[Object]
impl FeatureQuery {
/// 分页查询特征(系统管理)
async fn feature_paging(
&self,
ctx: &Context<'_>,
page: PageQueryDto,
query: FeatureQueryDto,
) -> async_graphql::Result<PageDto<FeatureDto>> {
let dba = ctx.data::<RtssDbAccessor>()?;
let paging = dba
.paging_query_features(page.into(), &query.into())
.await?;
Ok(paging.into())
}
/// id获取特征
async fn feature(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtssDbAccessor>()?;
let feature = dba.get_feature(id).await?;
Ok(feature.into())
}
/// id列表获取特征
async fn features(
&self,
ctx: &Context<'_>,
ids: Vec<i32>,
) -> async_graphql::Result<Vec<FeatureDto>> {
let dba = ctx.data::<RtssDbAccessor>()?;
let features = dba.get_features(ids.as_slice()).await?;
Ok(features.into_iter().map(|f| f.into()).collect())
}
}
#[Object]
impl FeatureMutation {
/// 上下架特征
async fn publish_feature(
&self,
ctx: &Context<'_>,
id: i32,
is_published: bool,
) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtssDbAccessor>()?;
let feature = dba.set_feature_published(id, is_published).await?;
Ok(feature.into())
}
}
#[derive(Debug, InputObject)]
pub struct FeatureQueryDto {
pub name: Option<String>,
pub feature_type: Option<i32>,
pub is_published: Option<bool>,
}
impl From<FeatureQueryDto> for rtss_db::FeaturePagingFilter {
fn from(value: FeatureQueryDto) -> Self {
Self {
name: value.name,
feature_type: value.feature_type,
is_published: value.is_published,
}
}
}
#[derive(Debug, SimpleObject)]
#[graphql(complex)]
pub struct FeatureDto {
pub id: i32,
pub feature_type: FeatureType,
pub name: String,
pub description: String,
pub config: Value,
pub is_published: bool,
pub creator_id: i32,
pub updater_id: i32,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[ComplexObject]
impl FeatureDto {
/// 创建用户name
async fn creator_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let name = loader.load_one(UserId::new(self.creator_id)).await?;
Ok(name)
}
/// 更新用户name
async fn updater_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let name = loader.load_one(UserId::new(self.updater_id)).await?;
Ok(name)
}
}
impl From<rtss_db::model::FeatureModel> for FeatureDto {
fn from(value: rtss_db::model::FeatureModel) -> Self {
Self {
id: value.id,
feature_type: value.feature_type,
name: value.name,
description: value.description,
config: value.config,
is_published: value.is_published,
creator_id: value.creator_id,
updater_id: value.updater_id,
created_at: value.created_at.naive_local(),
updated_at: value.updated_at.naive_local(),
}
}
}

View File

@ -1,105 +0,0 @@
use async_graphql::{Context, InputObject, Object};
use rtss_sim_manage::{AvailablePlugins, SimulationBuilder};
use super::{MutexSimulationManager, SimulationOperation};
#[derive(Default)]
pub struct SimulationQuery;
#[Object]
impl SimulationQuery {
async fn simulations<'ctx>(&self, ctx: &Context<'ctx>) -> usize {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.count()
}
}
#[derive(Default)]
pub struct SimulationMutation;
#[Object]
impl SimulationMutation {
async fn start_simulation<'ctx>(
&self,
ctx: &Context<'ctx>,
req: StartSimulationRequest,
) -> async_graphql::Result<String> {
// let claims = ctx.data::<Option<Claims>>().unwrap();
// match claims {
// Some(claims) => {
// info!("User {claims:?} started simulation");
// }
// _ => return Err("Unauthorized".into()),
// }
let sim = ctx.data::<MutexSimulationManager>().unwrap();
let id = sim.lock().await.start_simulation(
SimulationBuilder::default()
.id(req.user_id)
.plugins(vec![AvailablePlugins::TrackSideEquipmentPlugin]),
)?;
Ok(id)
}
async fn exit_simulation<'ctx>(
&self,
ctx: &Context<'ctx>,
id: String,
) -> async_graphql::Result<bool> {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.exit_simulation(id)?;
Ok(true)
}
async fn pause_simulation<'ctx>(
&self,
ctx: &Context<'ctx>,
id: String,
) -> async_graphql::Result<bool> {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.pause_simulation(id)?;
Ok(true)
}
async fn resume_simulation<'ctx>(
&self,
ctx: &Context<'ctx>,
id: String,
) -> async_graphql::Result<bool> {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.resume_simulation(id)?;
Ok(true)
}
async fn update_simulation_speed<'ctx>(
&self,
ctx: &Context<'ctx>,
id: String,
speed: f32,
) -> async_graphql::Result<bool> {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.update_simulation_speed(id, speed)?;
Ok(true)
}
async fn trigger_simulation_operation<'ctx>(
&self,
ctx: &Context<'ctx>,
id: String,
entity_uid: String,
operation: SimulationOperation,
) -> async_graphql::Result<bool> {
let sim = ctx.data::<MutexSimulationManager>().unwrap();
sim.lock().await.trigger_entity_operation(
id,
entity_uid,
operation.to_operation_event(),
)?;
Ok(true)
}
}
#[derive(InputObject)]
struct StartSimulationRequest {
user_id: String,
sim_def_id: String,
}

View File

@ -1,35 +0,0 @@
use std::ops::Deref;
use async_graphql::Enum;
use bevy_ecs::event::Event;
use rtss_sim_manage::SimulationManager;
use tokio::sync::Mutex;
pub struct MutexSimulationManager(Mutex<SimulationManager>);
impl Default for MutexSimulationManager {
fn default() -> Self {
Self(Mutex::new(SimulationManager::default()))
}
}
impl Deref for MutexSimulationManager {
type Target = Mutex<SimulationManager>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)]
pub enum SimulationOperation {
TurnoutControlDC,
TurnoutControlFC,
}
impl SimulationOperation {
pub fn to_operation_event(self) -> impl Event + Copy {
match self {
SimulationOperation::TurnoutControlDC => rtss_trackside::TurnoutControlEvent::DC,
SimulationOperation::TurnoutControlFC => rtss_trackside::TurnoutControlEvent::FC,
}
}
}

View File

@ -1,10 +0,0 @@
/// 数据库加载器
pub struct RtssDbLoader {
pub(crate) db_accessor: rtss_db::RtssDbAccessor,
}
impl RtssDbLoader {
pub fn new(db_accessor: rtss_db::RtssDbAccessor) -> Self {
Self { db_accessor }
}
}

View File

@ -1,6 +0,0 @@
[package]
name = "rtss_ci"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -1,14 +0,0 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@ -1,7 +0,0 @@
[package]
name = "rtss_common"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy_ecs = {workspace = true}

View File

@ -1,76 +0,0 @@
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use bevy_ecs::{component::Component, entity::Entity, system::Resource};
/// 仿真公共资源
pub struct SimulationResource {
id: String,
uid_entity_mapping: HashMap<String, Entity>,
}
impl SimulationResource {
pub fn new(id: String) -> Self {
SimulationResource {
id,
uid_entity_mapping: HashMap::new(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn get_entity(&self, uid: &str) -> Option<Entity> {
self.uid_entity_mapping.get(uid).cloned()
}
pub fn insert_entity(&mut self, uid: String, entity: Entity) {
self.uid_entity_mapping.insert(uid, entity);
}
}
// 设备编号组件
#[derive(Component, Debug, Clone, PartialEq, Eq)]
pub struct Uid(pub String);
impl Default for Uid {
fn default() -> Self {
Uid("".to_string())
}
}
#[derive(Resource)]
pub struct SharedSimulationResource(pub Arc<Mutex<SimulationResource>>);
impl SharedSimulationResource {
pub fn get_entity(&self, uid: &str) -> Option<Entity> {
self.0.lock().unwrap().uid_entity_mapping.get(uid).cloned()
}
pub fn insert_entity(&self, uid: String, entity: Entity) {
self.0
.lock()
.unwrap()
.uid_entity_mapping
.insert(uid, entity);
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::world;
use super::*;
#[test]
fn it_works() {
let mut simulation_resource = SimulationResource::new("1".to_string());
let mut world = world::World::default();
let uid = Uid("1".to_string());
let entity = world.spawn(uid.clone()).id();
simulation_resource.insert_entity(uid.clone().0, entity);
assert_eq!(simulation_resource.get_entity(&uid.0), Some(entity));
}
}

View File

@ -1,24 +0,0 @@
mod draft_data;
pub use draft_data::*;
mod release_data;
pub use release_data::*;
mod user;
pub use user::*;
mod feature;
pub use feature::*;
#[derive(Clone)]
pub struct RtssDbAccessor {
pool: sqlx::PgPool,
}
impl RtssDbAccessor {
pub fn new(pool: sqlx::PgPool) -> Self {
RtssDbAccessor { pool }
}
}
pub async fn get_db_accessor(url: &str) -> RtssDbAccessor {
let pool = sqlx::PgPool::connect(url).await.expect("连接数据库失败");
RtssDbAccessor::new(pool)
}

View File

@ -1,3 +0,0 @@
mod pb;
pub use pb::*;

View File

@ -1,205 +0,0 @@
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IscsGraphicStorage {
#[prost(message, repeated, tag = "1")]
pub cctv_of_station_control_storages: ::prost::alloc::vec::Vec<
CctvOfStationControlStorage,
>,
#[prost(message, repeated, tag = "2")]
pub fas_platform_alarm_storages: ::prost::alloc::vec::Vec<FasPlatformAlarmStorage>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UniqueIdOfStationLayout {
/// 城市
#[prost(string, tag = "1")]
pub city: ::prost::alloc::string::String,
/// 线路号
#[prost(string, tag = "2")]
pub line_id: ::prost::alloc::string::String,
/// 地图的公里标主坐标系
#[prost(string, tag = "3")]
pub main_coordinate_system: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Arrow {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
pub points: ::prost::alloc::vec::Vec<super::common::Point>,
}
/// Iscs文字
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IscsText {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(string, tag = "3")]
pub content: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub color: ::prost::alloc::string::String,
#[prost(int32, tag = "5")]
pub font_size: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Rect {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
/// 线宽
#[prost(int32, tag = "3")]
pub line_width: i32,
/// 线色
#[prost(string, tag = "4")]
pub line_color: ::prost::alloc::string::String,
/// 宽度
#[prost(float, tag = "5")]
pub width: f32,
/// 高度
#[prost(float, tag = "6")]
pub height: f32,
/// 圆角半径
#[prost(int32, tag = "7")]
pub radius: i32,
/// 画第一个点的坐标
#[prost(message, optional, tag = "8")]
pub point: ::core::option::Option<super::common::Point>,
}
/// CCTV按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CctvButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
#[prost(enumeration = "cctv_button::ButtonType", tag = "3")]
pub button_type: i32,
}
/// Nested message and enum types in `CCTVButton`.
pub mod cctv_button {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum ButtonType {
Rect = 0,
/// 监控样子的按钮
Monitor = 1,
/// 半圆样子的按钮
Semicircle = 2,
}
impl ButtonType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ButtonType::Rect => "rect",
ButtonType::Monitor => "monitor",
ButtonType::Semicircle => "semicircle",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"rect" => Some(Self::Rect),
"monitor" => Some(Self::Monitor),
"semicircle" => Some(Self::Semicircle),
_ => None,
}
}
}
}
/// 手动报警按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ManualAlarmButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 消防栓报警按钮
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HydrantAlarmButton {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 气体灭火
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GasExtinguishing {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 烟雾探测器
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SmokeDetector {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
/// 温度探测器
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TemperatureDetector {
#[prost(message, optional, tag = "1")]
pub common: ::core::option::Option<super::common::CommonInfo>,
#[prost(string, tag = "2")]
pub code: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CctvOfStationControlStorage {
#[prost(string, tag = "1")]
pub station_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub canvas: ::core::option::Option<super::common::Canvas>,
#[prost(message, repeated, tag = "3")]
pub arrows: ::prost::alloc::vec::Vec<Arrow>,
#[prost(message, repeated, tag = "4")]
pub iscs_texts: ::prost::alloc::vec::Vec<IscsText>,
#[prost(message, repeated, tag = "5")]
pub rects: ::prost::alloc::vec::Vec<Rect>,
#[prost(message, repeated, tag = "6")]
pub cctv_buttons: ::prost::alloc::vec::Vec<CctvButton>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FasPlatformAlarmStorage {
#[prost(string, tag = "1")]
pub station_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub canvas: ::core::option::Option<super::common::Canvas>,
#[prost(message, repeated, tag = "3")]
pub arrows: ::prost::alloc::vec::Vec<Arrow>,
#[prost(message, repeated, tag = "4")]
pub iscs_texts: ::prost::alloc::vec::Vec<IscsText>,
#[prost(message, repeated, tag = "5")]
pub rects: ::prost::alloc::vec::Vec<Rect>,
}

View File

@ -1,6 +0,0 @@
[package]
name = "rtss_iscs"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -1,14 +0,0 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@ -1,16 +0,0 @@
[package]
name = "rtss_sim_manage"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy_core = {workspace = true}
bevy_ecs = {workspace = true}
bevy_app = {workspace = true}
bevy_time = {workspace = true}
rayon = {workspace = true}
thiserror = {workspace = true}
rtss_log = { path = "../rtss_log" }
rtss_common = { path = "../rtss_common" }
rtss_trackside = { path = "../rtss_trackside" }

View File

@ -1,17 +0,0 @@
use bevy_app::App;
use rtss_trackside::TrackSideEquipmentPlugin;
#[derive(Debug)]
pub enum AvailablePlugins {
TrackSideEquipmentPlugin,
}
pub(crate) fn add_needed_plugins(app: &mut App, plugins: Vec<AvailablePlugins>) {
for plugin in plugins {
match plugin {
AvailablePlugins::TrackSideEquipmentPlugin => {
app.add_plugins(TrackSideEquipmentPlugin);
}
}
}
}

View File

@ -1,19 +0,0 @@
mod config_plugins;
mod simulation;
pub use config_plugins::*;
pub use simulation::*;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@ -1,468 +0,0 @@
use std::{
cell::RefCell,
collections::HashMap,
ops::Deref,
sync::{mpsc, Arc, Mutex},
time::{Duration, Instant},
};
use bevy_app::{prelude::*, PluginsState};
use bevy_ecs::{
event::{Event, EventWriter},
observer::Trigger,
system::{Query, Res, ResMut, Resource},
world::OnAdd,
};
use bevy_time::{prelude::*, TimePlugin};
use rtss_common::{SharedSimulationResource, SimulationResource, Uid};
use rtss_log::tracing::{debug, error, warn};
use thiserror::Error;
use crate::{add_needed_plugins, AvailablePlugins};
/// 仿真管理器
/// 非线程安全,若需要线程安全请使用类似 `Arc<Mutex<SimulationManager>>` 的方式
pub struct SimulationManager {
txs: RefCell<HashMap<String, Simulation>>,
}
impl Default for SimulationManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Error, Debug)]
pub enum SimulationControlError {
#[error("Unknown error")]
UnknownError,
#[error("Simulation not exist")]
SimulationNotExist,
#[error("Trigger event failed")]
TriggerEventFailed,
#[error("Simulation entity not exist")]
SimulationEntityNotExist,
}
impl SimulationManager {
fn new() -> Self {
let txs = RefCell::new(HashMap::new());
SimulationManager { txs }
}
pub fn count(&self) -> usize {
self.txs.borrow().len()
}
pub fn start_simulation(
&self,
builder: SimulationBuilder,
) -> Result<String, SimulationControlError> {
let id = builder.id.clone();
let sim = Simulation::new(builder);
self.txs.borrow_mut().insert(id.clone(), sim);
Ok(id)
}
pub fn exit_simulation(&self, id: String) -> Result<(), SimulationControlError> {
match self.txs.borrow_mut().remove(&id) {
Some(sim) => sim.exit_simulation(),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
pub fn pause_simulation(&self, id: String) -> Result<(), SimulationControlError> {
match self.txs.borrow().get(&id) {
Some(sim) => sim.pause_simulation(),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
pub fn resume_simulation(&self, id: String) -> Result<(), SimulationControlError> {
match self.txs.borrow().get(&id) {
Some(sim) => sim.resume_simulation(),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
pub fn update_simulation_speed(
&self,
id: String,
speed: f32,
) -> Result<(), SimulationControlError> {
match self.txs.borrow().get(&id) {
Some(sim) => sim.update_simulation_speed(speed),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
pub fn trigger_operation<E>(&self, id: String, event: E) -> Result<(), SimulationControlError>
where
E: Event + Copy,
{
match self.txs.borrow().get(&id) {
Some(sim) => sim.trigger_operation(event),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
pub fn trigger_entity_operation<E>(
&self,
id: String,
entity_uid: String,
event: E,
) -> Result<(), SimulationControlError>
where
E: Event + Copy,
{
match self.txs.borrow().get(&id) {
Some(sim) => sim.trigger_entity_operation(entity_uid, event),
None => {
warn!("Simulation not exist, id={}", id);
Err(SimulationControlError::SimulationNotExist)
}
}
}
}
pub struct SimulationBuilder {
/// 仿真ID
pub(crate) id: String,
/// 仿真主逻辑循环间隔,详细请查看 [`Time<Fixed>`](bevy_time::fixed::Fixed)
pub(crate) loop_duration: Duration,
/// 仿真所需插件
pub(crate) plugins: Vec<AvailablePlugins>,
}
impl Default for SimulationBuilder {
fn default() -> Self {
SimulationBuilder {
id: "default".to_string(),
loop_duration: Duration::from_millis(500),
plugins: Vec::new(),
}
}
}
impl SimulationBuilder {
pub fn id(mut self, id: String) -> Self {
self.id = id;
self
}
pub fn loop_duration(mut self, loop_duration: Duration) -> Self {
self.loop_duration = loop_duration;
self
}
pub fn plugins(mut self, plugins: Vec<AvailablePlugins>) -> Self {
self.plugins = plugins;
self
}
}
#[derive(Resource, Debug)]
pub struct SimulationId(String);
impl SimulationId {
pub fn new(id: String) -> Self {
SimulationId(id)
}
}
impl Deref for SimulationId {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Resource, Debug)]
pub struct SimulationStatus {
// 仿真倍速
pub speed: f32,
// 仿真是否暂停状态
pub paused: bool,
}
impl Default for SimulationStatus {
fn default() -> Self {
SimulationStatus {
speed: 1.0,
paused: true,
}
}
}
/// 仿真控制事件
#[derive(Event, Debug, Clone, Copy)]
pub enum SimulationControlEvent {
Pause,
Unpause,
UpdateSpeed(f32),
Exit,
}
pub struct Simulation {
tx: mpsc::Sender<Box<SimulationHandle>>,
resource: Arc<Mutex<SimulationResource>>,
}
pub type SimulationHandle = dyn FnMut(&mut App) + Send;
impl Simulation {
pub fn new(builder: SimulationBuilder) -> Self {
let simulation_resource = Arc::new(Mutex::new(SimulationResource::new(builder.id.clone())));
let cloned_resource = Arc::clone(&simulation_resource);
let (tx, mut rx) = mpsc::channel();
rayon::spawn(move || {
let mut app = App::new();
let mut virtual_time =
Time::<Virtual>::from_max_delta(builder.loop_duration.mul_f32(2f32));
virtual_time.pause();
// 初始化仿真App
app.add_plugins(TimePlugin)
.insert_resource(virtual_time)
.insert_resource(Time::<Fixed>::from_duration(builder.loop_duration))
.insert_resource(SimulationId::new(builder.id))
.insert_resource(SimulationStatus::default())
.insert_resource(SharedSimulationResource(Arc::clone(&cloned_resource)))
.add_event::<SimulationControlEvent>()
.observe(simulation_status_control)
.observe(entity_observer);
// 添加仿真所需插件
add_needed_plugins(&mut app, builder.plugins);
let wait = Some(builder.loop_duration);
app.set_runner(move |mut app: App| {
let plugins_state = app.plugins_state();
if plugins_state != PluginsState::Cleaned {
app.finish();
app.cleanup();
}
loop {
match runner(&mut app, wait, &mut rx) {
Ok(Some(delay)) => std::thread::sleep(delay),
Ok(None) => continue,
Err(exit) => return exit,
}
}
});
app.run();
});
Simulation {
tx,
resource: simulation_resource,
}
}
fn trigger_event(&self, event: SimulationControlEvent) -> Result<(), SimulationControlError> {
let id = self.resource.lock().unwrap().id().to_string();
let result = self.tx.send(Box::new(move |app: &mut App| {
app.world_mut().trigger(event);
}));
match result {
Ok(_) => Ok(()),
Err(e) => {
error!(
"Failed to send event to simulation, id={}, error={:?}",
id, e
);
Err(SimulationControlError::TriggerEventFailed)
}
}
}
pub fn trigger_operation<E>(&self, event: E) -> Result<(), SimulationControlError>
where
E: Event + Copy,
{
let id = self.resource.lock().unwrap().id().to_string();
let result = self.tx.send(Box::new(move |app: &mut App| {
app.world_mut().trigger(event);
}));
match result {
Ok(_) => Ok(()),
Err(e) => {
error!(
"Failed to send event to simulation, id={}, error={:?}",
id, e
);
Err(SimulationControlError::TriggerEventFailed)
}
}
}
pub fn trigger_entity_operation<E>(
&self,
entity_uid: String,
event: E,
) -> Result<(), SimulationControlError>
where
E: Event + Copy,
{
let id = self.resource.lock().unwrap().id().to_string();
match self.resource.lock().unwrap().get_entity(&entity_uid) {
Some(entity) => {
let result = self.tx.send(Box::new(move |app: &mut App| {
app.world_mut().trigger_targets(event, entity);
}));
match result {
Ok(_) => Ok(()),
Err(e) => {
error!(
"Failed to send event to simulation, id={}, error={:?}",
id, e
);
Err(SimulationControlError::TriggerEventFailed)
}
}
}
None => {
error!("Entity not exist, id={}", entity_uid);
Err(SimulationControlError::SimulationEntityNotExist)
}
}
}
pub fn exit_simulation(&self) -> Result<(), SimulationControlError> {
self.trigger_event(SimulationControlEvent::Exit)
}
pub fn pause_simulation(&self) -> Result<(), SimulationControlError> {
self.trigger_event(SimulationControlEvent::Pause)
}
pub fn resume_simulation(&self) -> Result<(), SimulationControlError> {
self.trigger_event(SimulationControlEvent::Unpause)
}
pub fn update_simulation_speed(&self, speed: f32) -> Result<(), SimulationControlError> {
self.trigger_event(SimulationControlEvent::UpdateSpeed(speed))
}
}
pub fn entity_observer(
trigger: Trigger<OnAdd>,
query: Query<&Uid>,
shared: ResMut<SharedSimulationResource>,
) {
let entity = trigger.entity();
match query.get(entity) {
Ok(uid) => {
shared.insert_entity(uid.0.clone(), entity);
debug!("添加uid实体映射, Uid: {:?}, Entity: {:?}", uid, entity);
}
Err(_) => {
warn!("Failed to get Uid from entity: {:?}", entity);
}
}
}
pub fn simulation_status_control(
trigger: Trigger<SimulationControlEvent>,
mut time: ResMut<Time<Virtual>>,
sid: Res<SimulationId>,
mut exit: EventWriter<AppExit>,
) {
match trigger.event() {
SimulationControlEvent::Pause => {
debug!("Pausing simulation");
time.pause();
}
SimulationControlEvent::Unpause => {
debug!("Unpausing simulation");
time.unpause();
}
SimulationControlEvent::UpdateSpeed(speed) => {
debug!("Update simulation speed to {}", speed);
time.set_relative_speed(*speed);
}
SimulationControlEvent::Exit => {
debug!("Exiting simulation, id={:?}", *sid);
exit.send(AppExit::Success);
}
}
}
fn runner(
app: &mut App,
wait: Option<Duration>,
rx: &mut mpsc::Receiver<Box<SimulationHandle>>,
) -> Result<Option<Duration>, AppExit> {
let start_time = Instant::now();
if let Err(e) = rx.try_recv().map(|mut handle| handle(app)) {
match e {
mpsc::TryRecvError::Empty => {}
mpsc::TryRecvError::Disconnected => {
error!("Simulation handle channel disconnected");
}
}
}
app.update();
if let Some(exit) = app.should_exit() {
return Err(exit);
};
let end_time = Instant::now();
if let Some(wait) = wait {
let exe_time = end_time - start_time;
if exe_time < wait {
return Ok(Some(wait - exe_time));
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simulation_manager() {
let manager = SimulationManager::default();
assert_eq!(manager.count(), 0);
if let Ok(_) = manager.start_simulation(SimulationBuilder::default().id("0".to_string())) {
assert_eq!(manager.count(), 1);
}
if let Ok(_) = manager.start_simulation(SimulationBuilder::default().id("1".to_string())) {
assert_eq!(manager.count(), 2);
}
if let Ok(_) = manager.exit_simulation("0".to_string()) {
assert_eq!(manager.count(), 1);
}
if let Ok(_) = manager.exit_simulation("1".to_string()) {
assert_eq!(manager.count(), 0);
}
}
}

View File

@ -1,13 +0,0 @@
[package]
name = "rtss_trackside"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy_core = {workspace = true}
bevy_ecs = {workspace = true}
bevy_app = {workspace = true}
bevy_time = {workspace = true}
rtss_log = { path = "../rtss_log" }
rtss_common = { path = "../rtss_common" }

View File

@ -1,10 +0,0 @@
mod components;
mod events;
mod plugin;
mod resources;
mod systems;
pub use components::*;
pub use events::*;
pub use plugin::*;
pub use resources::*;
pub use systems::*;

15
manager/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "manager"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
rtsa_log = { path = "../crates/rtsa_log" }
rtsa_api = { path = "crates/rtsa_api" }
rtsa_db = { path = "../crates/rtsa_db" }
serde = { workspace = true }
config = { workspace = true }
clap = { workspace = true, features = ["derive"] }
enum_dispatch = { workspace = true }
anyhow = { workspace = true }

View File

@ -11,11 +11,11 @@ ENV TZ=Asia/Shanghai
# 复制时区信息到系统时区目录
RUN cp /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /rtss_sim
COPY ./target/x86_64-unknown-linux-musl/release/rtss_simulation ./rtss_sim
WORKDIR /rtsa
COPY ./target/x86_64-unknown-linux-musl/release/manager ./rtsa_m
COPY ./conf/* ./conf/
COPY ./migrations/* ./migrations/
EXPOSE 8765
CMD ["sh", "-c", "./rtss_sim db migrate && ./rtss_sim serve"]
CMD ["sh", "-c", "./rtsa_m db migrate && ./rtsa_m serve"]

View File

@ -2,4 +2,4 @@
url = "postgresql://joylink:Joylink@0503@localhost:5432/joylink"
[sso]
base_url = "http://192.168.33.233/rtss-server"
base_url = "http://192.168.33.233/rtsa-server"

View File

@ -5,4 +5,4 @@ url = "postgresql://joylink:Joylink@0503@10.11.11.2:5432/joylink"
level = "debug"
[sso]
base_url = "http://192.168.33.233/rtss-server"
base_url = "http://192.168.33.233/rtsa-server"

View File

@ -1,5 +1,5 @@
[package]
name = "rtss_api"
name = "rtsa_api"
version = "0.1.0"
edition = "2021"
@ -19,9 +19,6 @@ base64 = "0.22.1"
sysinfo = "0.31.3"
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls", "json"] }
bevy_ecs = { workspace = true }
rtss_log = { path = "../rtss_log" }
rtss_sim_manage = { path = "../rtss_sim_manage" }
rtss_trackside = { path = "../rtss_trackside" }
rtss_db = { path = "../rtss_db" }
rtss_dto = { path = "../rtss_dto" }
rtsa_log = { path = "../../../crates/rtsa_log" }
rtsa_db = { path = "../../../crates/rtsa_db" }
rtsa_dto = { path = "../../../crates/rtsa_dto" }

View File

@ -1,6 +1,6 @@
use async_graphql::{InputObject, InputType, OutputType, SimpleObject};
use rtss_db::{common::TableColumn, model::DraftDataColumn};
use rtss_dto::common::IscsStyle;
use rtsa_db::{common::TableColumn, model::DraftDataColumn};
use rtsa_dto::common::IscsStyle;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
@ -10,7 +10,7 @@ pub trait DataOptions: InputType + OutputType + Serialize + DeserializeOwned {
impl DataOptions for Value {
fn to_data_options_filter_clause(&self) -> String {
format!("options @> '{}'", self)
format!("{} @> '{}'", DraftDataColumn::Options.name(), self)
}
}
@ -36,7 +36,7 @@ mod tests {
#[test]
fn test_iscs_data_options_serialize() {
rtss_log::Logging::default().init();
rtsa_log::Logging::default().init();
let options = IscsDataOptions {
style: IscsStyle::DaShiZhiNeng,
};

View File

@ -3,15 +3,15 @@ use async_graphql::{ComplexObject, Context, InputObject, Object, SimpleObject};
use base64::prelude::*;
use base64::Engine;
use chrono::NaiveDateTime;
use rtss_db::DraftDataAccessor;
use rtss_db::RtssDbAccessor;
use rtss_dto::common::{DataType, Role};
use rtsa_db::DraftDataAccessor;
use rtsa_db::RtsaDbAccessor;
use rtsa_dto::common::{DataType, Role};
use serde_json::Value;
use crate::apis::{PageDto, PageQueryDto};
use crate::loader::RtssDbLoader;
use crate::loader::RtsaDbLoader;
use super::common::{DataOptions, IscsDataOptions};
use super::data_options_def::{DataOptions, IscsDataOptions};
use super::release_data::ReleaseDataId;
use super::user::UserId;
@ -33,7 +33,7 @@ impl DraftDataQuery {
paging: PageQueryDto,
query: DraftDataFilterDto<Value>,
) -> async_graphql::Result<PageDto<DraftDataDto>> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let paging_result = db_accessor
.paging_query_draft_data(query.into(), paging.into())
.await?;
@ -53,7 +53,7 @@ impl DraftDataQuery {
.await?;
query.user_id = user.id_i32();
query.data_type = Some(DataType::Iscs);
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let paging_result = db_accessor
.paging_query_draft_data(query.into(), paging.into())
.await?;
@ -67,7 +67,7 @@ impl DraftDataQuery {
paging: PageQueryDto,
mut query: DraftDataFilterDto<IscsDataOptions>,
) -> async_graphql::Result<PageDto<DraftIscsDataDto>> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
query.data_type = Some(DataType::Iscs);
let paging_result = db_accessor
.paging_query_draft_data(query.into(), paging.into())
@ -77,7 +77,7 @@ impl DraftDataQuery {
/// 根据id获取草稿数据
#[graphql(guard = "RoleGuard::new(Role::User)")]
async fn draft_data(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor.query_draft_data_by_id(id).await?;
Ok(draft_data.into())
}
@ -94,7 +94,7 @@ impl DraftDataQuery {
.query_user(&ctx.data::<Token>()?.0)
.await?;
let user_id = user.id_i32();
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let exist = db_accessor
.is_draft_data_exist(user_id, &data_type, &name)
.await?;
@ -115,8 +115,8 @@ impl DraftDataMutation {
.data::<UserAuthCache>()?
.query_user(&ctx.data::<Token>()?.0)
.await?;
input = input.with_user_id(user.id_i32());
let db_accessor = ctx.data::<RtssDbAccessor>()?;
input = input.with_data_type_and_user_id(DataType::Iscs, user.id_i32());
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor.create_draft_data(input.into()).await?;
Ok(draft_data.into())
}
@ -128,7 +128,7 @@ impl DraftDataMutation {
id: i32,
name: String,
) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor.update_draft_data_name(id, &name).await?;
Ok(draft_data.into())
}
@ -141,7 +141,7 @@ impl DraftDataMutation {
id: i32,
data: String, // base64编码的数据
) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let bytes = BASE64_STANDARD
.decode(data)
.map_err(|e| async_graphql::Error::new(format!("base64 decode error: {}", e)))?;
@ -156,7 +156,7 @@ impl DraftDataMutation {
id: i32,
is_shared: bool,
) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor.set_draft_data_shared(id, is_shared).await?;
Ok(draft_data.into())
}
@ -167,7 +167,7 @@ impl DraftDataMutation {
ctx: &Context<'_>,
id: Vec<i32>,
) -> async_graphql::Result<bool> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
db_accessor.delete_draft_data(id.as_slice()).await?;
Ok(true)
}
@ -179,7 +179,7 @@ impl DraftDataMutation {
id: i32,
release_data_id: i32,
) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor
.set_default_release_data_id(id, release_data_id)
.await?;
@ -198,7 +198,7 @@ impl DraftDataMutation {
.query_user(&ctx.data::<Token>()?.0)
.await?;
let user_id = user.id_i32();
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let draft_data = db_accessor.save_as_new_draft(id, &name, user_id).await?;
Ok(draft_data.into())
}
@ -207,6 +207,8 @@ impl DraftDataMutation {
#[derive(Debug, InputObject)]
#[graphql(concrete(name = "CreateDraftIscsDto", params(IscsDataOptions)))]
pub struct CreateDraftDataDto<T: DataOptions> {
#[graphql(skip)]
pub data_type: Option<DataType>,
pub name: String,
pub options: Option<T>,
#[graphql(skip)]
@ -214,17 +216,18 @@ pub struct CreateDraftDataDto<T: DataOptions> {
}
impl<T: DataOptions> CreateDraftDataDto<T> {
pub fn with_user_id(mut self, id: i32) -> Self {
pub fn with_data_type_and_user_id(mut self, data_type: DataType, id: i32) -> Self {
self.data_type = Some(data_type);
self.user_id = Some(id);
self
}
}
impl<T: DataOptions> From<CreateDraftDataDto<T>> for rtss_db::CreateDraftData {
impl<T: DataOptions> From<CreateDraftDataDto<T>> for rtsa_db::CreateDraftData {
fn from(value: CreateDraftDataDto<T>) -> Self {
let cdd = Self::new(
&value.name,
DataType::Iscs,
value.data_type.expect("need data_type"),
value.user_id.expect("CreateDraftDataDto need user_id"),
);
if value.options.is_some() {
@ -243,12 +246,12 @@ pub struct UserDraftDataFilterDto<T: DataOptions> {
pub user_id: i32,
pub name: Option<String>,
/// 数据类型,在某个具体类型查询时不传,传了也不生效
pub data_type: Option<rtss_dto::common::DataType>,
pub data_type: Option<rtsa_dto::common::DataType>,
pub options: Option<T>,
pub is_shared: Option<bool>,
}
impl<T: DataOptions> From<UserDraftDataFilterDto<T>> for rtss_db::DraftDataQuery {
impl<T: DataOptions> From<UserDraftDataFilterDto<T>> for rtsa_db::DraftDataQuery {
fn from(value: UserDraftDataFilterDto<T>) -> Self {
Self {
user_id: Some(value.user_id),
@ -274,7 +277,7 @@ pub struct DraftDataFilterDto<T: DataOptions> {
pub options: Option<T>,
}
impl<T: DataOptions> From<DraftDataFilterDto<T>> for rtss_db::DraftDataQuery {
impl<T: DataOptions> From<DraftDataFilterDto<T>> for rtsa_db::DraftDataQuery {
fn from(value: DraftDataFilterDto<T>) -> Self {
Self {
user_id: value.user_id,
@ -310,7 +313,7 @@ impl DraftDataDto {
ctx: &Context<'_>,
) -> async_graphql::Result<Option<String>> {
if let Some(version_id) = self.default_release_data_id {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(ReleaseDataId::new(version_id)).await?;
Ok(name)
} else {
@ -320,14 +323,14 @@ impl DraftDataDto {
/// 获取用户name
async fn user_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(UserId::new(self.user_id)).await?;
Ok(name)
}
}
impl From<rtss_db::model::DraftDataModel> for DraftDataDto {
fn from(value: rtss_db::model::DraftDataModel) -> Self {
impl From<rtsa_db::model::DraftDataModel> for DraftDataDto {
fn from(value: rtsa_db::model::DraftDataModel) -> Self {
Self {
id: value.id,
name: value.name,
@ -351,8 +354,8 @@ pub struct DraftIscsDataDto {
pub options: Option<IscsDataOptions>,
}
impl From<rtss_db::model::DraftDataModel> for DraftIscsDataDto {
fn from(value: rtss_db::model::DraftDataModel) -> Self {
impl From<rtsa_db::model::DraftDataModel> for DraftIscsDataDto {
fn from(value: rtsa_db::model::DraftDataModel) -> Self {
Self {
options: value
.options

View File

@ -0,0 +1,228 @@
use crate::{
apis::{PageDto, PageQueryDto},
loader::RtsaDbLoader,
user_auth::{RoleGuard, Token, UserAuthCache},
};
use async_graphql::{
dataloader::DataLoader, ComplexObject, Context, InputObject, Object, SimpleObject,
};
use chrono::NaiveDateTime;
use rtsa_db::{CreateFeature, FeatureAccessor, RtsaDbAccessor, UpdateFeature};
use rtsa_dto::common::FeatureType;
use rtsa_dto::common::Role;
use serde_json::Value;
use super::{
feature_config_def::{FeatureConfig, UrFeatureConfig},
user::UserId,
};
#[derive(Default)]
pub struct FeatureQuery;
#[derive(Default)]
pub struct FeatureMutation;
#[Object]
impl FeatureQuery {
/// 分页查询功能feature(系统管理)
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
async fn feature_paging(
&self,
ctx: &Context<'_>,
page: PageQueryDto,
query: FeatureQueryDto,
) -> async_graphql::Result<PageDto<FeatureDto>> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let paging = dba
.paging_query_features(page.into(), &query.into())
.await?;
Ok(paging.into())
}
/// id获取功能feature
#[graphql(guard = "RoleGuard::new(Role::User)")]
async fn feature(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let feature = dba.get_feature(id).await?;
Ok(feature.into())
}
/// id列表获取功能feature列表
#[graphql(guard = "RoleGuard::new(Role::User)")]
async fn features(
&self,
ctx: &Context<'_>,
ids: Vec<i32>,
) -> async_graphql::Result<Vec<FeatureDto>> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let features = dba.get_features(ids.as_slice()).await?;
Ok(features.into_iter().map(|f| f.into()).collect())
}
}
#[Object]
impl FeatureMutation {
/// 上下架功能feature
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
async fn publish_feature(
&self,
ctx: &Context<'_>,
id: i32,
is_published: bool,
) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let feature = dba.set_feature_published(id, is_published).await?;
Ok(feature.into())
}
/// 创建城轨仿真功能feature
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
async fn create_ur_feature(
&self,
ctx: &Context<'_>,
mut input: CreateFeatureDto<UrFeatureConfig>,
) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let user = ctx
.data::<UserAuthCache>()?
.query_user(&ctx.data::<Token>()?.0)
.await?;
input = input.with_feature_type_and_user_id(FeatureType::Ur, user.id_i32());
let feature = dba.create_feature(&input.into()).await?;
Ok(feature.into())
}
/// 更新城轨仿真功能feature
#[graphql(guard = "RoleGuard::new(Role::Admin)")]
async fn update_ur_feature(
&self,
ctx: &Context<'_>,
input: UpdateFeatureDto<UrFeatureConfig>,
) -> async_graphql::Result<FeatureDto> {
let dba = ctx.data::<RtsaDbAccessor>()?;
let feature = dba.update_feature(&input.into()).await?;
Ok(feature.into())
}
}
#[derive(Debug, InputObject)]
#[graphql(concrete(name = "UpdateUrFeatureDto", params(UrFeatureConfig)))]
pub struct UpdateFeatureDto<T: FeatureConfig> {
pub id: i32,
pub name: String,
pub description: String,
pub config: T,
#[graphql(skip)]
pub user_id: Option<i32>,
}
impl<T: FeatureConfig> From<UpdateFeatureDto<T>> for UpdateFeature {
fn from(value: UpdateFeatureDto<T>) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
config: serde_json::to_value(&value.config).expect("config is to_value failed"),
updater_id: value.user_id.expect("user_id must be set"),
}
}
}
#[derive(Debug, InputObject)]
#[graphql(concrete(name = "CreateUrFeatureDto", params(UrFeatureConfig)))]
pub struct CreateFeatureDto<T: FeatureConfig> {
#[graphql(skip)]
pub feature_type: Option<FeatureType>,
pub name: String,
pub description: String,
pub config: T,
#[graphql(skip)]
pub user_id: Option<i32>,
}
impl<T: FeatureConfig> From<CreateFeatureDto<T>> for CreateFeature {
fn from(value: CreateFeatureDto<T>) -> Self {
Self {
feature_type: value.feature_type.expect("feature_type must be set"),
name: value.name,
description: value.description,
config: serde_json::to_value(&value.config).expect("config is to_value failed"),
creator_id: value.user_id.expect("user_id must be set"),
}
}
}
impl<T: FeatureConfig> CreateFeatureDto<T> {
fn with_feature_type_and_user_id(mut self, feature_type: FeatureType, uid: i32) -> Self {
self.feature_type = Some(feature_type);
self.user_id = Some(uid);
self
}
}
#[derive(Debug, InputObject)]
pub struct FeatureQueryDto {
pub name: Option<String>,
pub feature_type: Option<i32>,
pub is_published: Option<bool>,
}
impl From<FeatureQueryDto> for rtsa_db::FeaturePagingFilter {
fn from(value: FeatureQueryDto) -> Self {
Self {
name: value.name,
feature_type: value.feature_type,
is_published: value.is_published,
}
}
}
#[derive(Debug, SimpleObject)]
#[graphql(complex)]
pub struct FeatureDto {
pub id: i32,
pub feature_type: FeatureType,
pub name: String,
pub description: String,
pub config: Value,
pub is_published: bool,
pub creator_id: i32,
pub updater_id: i32,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[ComplexObject]
impl FeatureDto {
/// 创建用户name
async fn creator_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(UserId::new(self.creator_id)).await?;
Ok(name)
}
/// 更新用户name
async fn updater_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(UserId::new(self.updater_id)).await?;
Ok(name)
}
}
impl From<rtsa_db::model::FeatureModel> for FeatureDto {
fn from(value: rtsa_db::model::FeatureModel) -> Self {
Self {
id: value.id,
feature_type: value.feature_type,
name: value.name,
description: value.description,
config: value.config,
is_published: value.is_published,
creator_id: value.creator_id,
updater_id: value.updater_id,
created_at: value.created_at.naive_local(),
updated_at: value.updated_at.naive_local(),
}
}
}

View File

@ -0,0 +1,17 @@
use async_graphql::{InputObject, InputType, OutputType, SimpleObject};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
pub trait FeatureConfig: InputType + OutputType + Serialize + DeserializeOwned {}
impl FeatureConfig for Value {}
/// UR功能配置
#[derive(Debug, Clone, InputObject, SimpleObject, Serialize, Deserialize)]
#[graphql(input_name = "UrFeatureConfigInput")]
pub struct UrFeatureConfig {
/// 电子地图id
pub ems: Vec<i32>,
}
impl FeatureConfig for UrFeatureConfig {}

View File

@ -3,16 +3,14 @@ use draft_data::{DraftDataMutation, DraftDataQuery};
use feature::{FeatureMutation, FeatureQuery};
use release_data::{ReleaseDataMutation, ReleaseDataQuery};
mod simulation_definition;
mod sys_info;
use simulation_definition::*;
use user::{UserMutation, UserQuery};
mod common;
mod data_options_def;
mod draft_data;
mod feature;
mod feature_config_def;
mod release_data;
mod simulation;
mod user;
#[derive(Default, MergedObject)]
@ -27,7 +25,7 @@ pub struct Mutation(
);
#[derive(Enum, Copy, Clone, Default, Eq, PartialEq, Debug)]
#[graphql(remote = "rtss_db::common::SortOrder")]
#[graphql(remote = "rtsa_db::common::SortOrder")]
pub enum SortOrder {
#[default]
Asc,
@ -42,7 +40,7 @@ pub struct PageQueryDto {
pub items_per_page: i32,
}
impl From<PageQueryDto> for rtss_db::common::PageQuery {
impl From<PageQueryDto> for rtsa_db::common::PageQuery {
fn from(value: PageQueryDto) -> Self {
Self {
page: value.page,
@ -76,8 +74,8 @@ impl<T: OutputType> PageDto<T> {
}
}
impl<T: OutputType, M: Into<T>> From<rtss_db::common::PageResult<M>> for PageDto<T> {
fn from(value: rtss_db::common::PageResult<M>) -> Self {
impl<T: OutputType, M: Into<T>> From<rtsa_db::common::PageResult<M>> for PageDto<T> {
fn from(value: rtsa_db::common::PageResult<M>) -> Self {
Self::new(
value.total,
value.data.into_iter().map(|m| m.into()).collect(),

View File

@ -5,16 +5,16 @@ use async_graphql::dataloader::*;
use async_graphql::{ComplexObject, Context, InputObject, Object, SimpleObject};
use base64::prelude::*;
use chrono::NaiveDateTime;
use rtss_db::model::*;
use rtss_db::prelude::*;
use rtss_db::{model::ReleaseDataModel, ReleaseDataAccessor, RtssDbAccessor};
use rtss_dto::common::{DataType, Role};
use rtsa_db::model::*;
use rtsa_db::prelude::*;
use rtsa_db::{model::ReleaseDataModel, ReleaseDataAccessor, RtsaDbAccessor};
use rtsa_dto::common::{DataType, Role};
use serde_json::Value;
use crate::apis::draft_data::DraftDataDto;
use crate::loader::RtssDbLoader;
use crate::loader::RtsaDbLoader;
use super::common::{DataOptions, IscsDataOptions};
use super::data_options_def::{DataOptions, IscsDataOptions};
use super::user::UserId;
use super::{PageDto, PageQueryDto};
@ -36,7 +36,7 @@ impl ReleaseDataQuery {
page: PageQueryDto,
query: ReleaseTypedDataFilterDto<Value>,
) -> async_graphql::Result<PageDto<ReleaseDataDto>> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let paging = db_accessor
.paging_query_release_data_list(query.into(), page.into())
.await?;
@ -51,7 +51,7 @@ impl ReleaseDataQuery {
page: PageQueryDto,
mut query: ReleaseTypedDataFilterDto<IscsDataOptions>,
) -> async_graphql::Result<PageDto<ReleaseIscsDataWithoutVersionDto>> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
query.data_type = Some(DataType::Iscs);
let paging = db_accessor
.paging_query_release_data_list(query.into(), page.into())
@ -66,7 +66,7 @@ impl ReleaseDataQuery {
ctx: &Context<'_>,
id: i32,
) -> async_graphql::Result<ReleaseDataWithUsedVersionDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let model = db_accessor.query_release_data_with_used_version(id).await?;
Ok(model.into())
}
@ -79,7 +79,7 @@ impl ReleaseDataQuery {
data_type: DataType,
name: String,
) -> async_graphql::Result<bool> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.is_release_data_name_exist(data_type, &name)
.await?;
@ -94,7 +94,7 @@ impl ReleaseDataQuery {
data_id: i32,
page: PageQueryDto,
) -> async_graphql::Result<PageDto<ReleaseDataVersionDto>> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let paging = db_accessor
.paging_query_release_data_version_list(data_id, page.into())
.await?;
@ -108,7 +108,7 @@ impl ReleaseDataQuery {
ctx: &Context<'_>,
version_id: i32,
) -> async_graphql::Result<ReleaseDataVersionDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let model = db_accessor
.query_release_data_version_by_id(version_id)
.await?;
@ -132,7 +132,7 @@ impl ReleaseDataMutation {
.query_user(&ctx.data::<Token>()?.0)
.await?;
let user_id = user.id_i32();
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.release_new_from_draft(draft_id, &name, &description, Some(user_id))
.await?;
@ -152,7 +152,7 @@ impl ReleaseDataMutation {
.query_user(&ctx.data::<Token>()?.0)
.await?;
let user_id = user.id_i32();
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.release_to_existing(draft_id, &description, Some(user_id))
.await?;
@ -167,7 +167,7 @@ impl ReleaseDataMutation {
id: i32,
name: String,
) -> async_graphql::Result<ReleaseDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor.update_release_data_name(id, &name).await?;
Ok(result.into())
}
@ -180,7 +180,7 @@ impl ReleaseDataMutation {
id: i32,
is_published: bool,
) -> async_graphql::Result<ReleaseDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.set_release_data_published(id, is_published)
.await?;
@ -195,7 +195,7 @@ impl ReleaseDataMutation {
id: i32,
version_id: i32,
) -> async_graphql::Result<ReleaseDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.set_release_data_used_version(id, version_id)
.await?;
@ -214,7 +214,7 @@ impl ReleaseDataMutation {
.query_user(&ctx.data::<Token>()?.0)
.await?;
let user_id = user.id_i32();
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let db_accessor = ctx.data::<RtsaDbAccessor>()?;
let result = db_accessor
.create_draft_from_release_version(version_id, user_id)
.await?;
@ -235,7 +235,7 @@ pub struct ReleaseTypedDataFilterDto<T: DataOptions> {
pub is_published: Option<bool>,
}
impl<T: DataOptions> From<ReleaseTypedDataFilterDto<T>> for rtss_db::ReleaseDataQuery {
impl<T: DataOptions> From<ReleaseTypedDataFilterDto<T>> for rtsa_db::ReleaseDataQuery {
fn from(value: ReleaseTypedDataFilterDto<T>) -> Self {
Self {
name: value.name,
@ -265,7 +265,7 @@ pub struct ReleaseDataDto {
impl ReleaseDataDto {
async fn description(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
if let Some(version_id) = self.used_version_id {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let description = loader
.load_one(ReleaseDataVersionId::new(version_id))
.await?;
@ -277,7 +277,7 @@ impl ReleaseDataDto {
/// 获取用户name
async fn user_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(UserId::new(self.user_id)).await?;
Ok(name)
}
@ -294,7 +294,7 @@ impl ReleaseDataId {
}
}
impl Loader<ReleaseDataId> for RtssDbLoader {
impl Loader<ReleaseDataId> for RtsaDbLoader {
type Value = String;
type Error = Arc<DbAccessError>;
@ -326,7 +326,7 @@ impl ReleaseDataVersionId {
}
}
impl Loader<ReleaseDataVersionId> for RtssDbLoader {
impl Loader<ReleaseDataVersionId> for RtsaDbLoader {
type Value = String;
type Error = Arc<DbAccessError>;
@ -388,7 +388,7 @@ pub struct ReleaseDataVersionDto {
impl ReleaseDataVersionDto {
/// 获取用户name
async fn user_name(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let loader = ctx.data_unchecked::<DataLoader<RtsaDbLoader>>();
let name = loader.load_one(UserId::new(self.user_id)).await?;
Ok(name)
}

View File

@ -2,14 +2,14 @@ use std::{collections::HashMap, sync::Arc};
use async_graphql::{dataloader::Loader, Context, InputObject, Object, SimpleObject};
use chrono::NaiveDateTime;
use rtss_db::{DbAccessError, RtssDbAccessor, UserAccessor};
use rtsa_db::{DbAccessError, RtsaDbAccessor, UserAccessor};
use crate::{
loader::RtssDbLoader,
loader::RtsaDbLoader,
user_auth::{build_jwt, Claims, RoleGuard, Token, UserAuthCache, UserInfoDto},
UserAuthClient,
};
use rtss_dto::common::Role;
use rtsa_dto::common::Role;
use super::{PageDto, PageQueryDto};
@ -47,7 +47,7 @@ impl UserQuery {
page: PageQueryDto,
query: UserQueryDto,
) -> async_graphql::Result<PageDto<UserDto>> {
let dba = ctx.data::<RtssDbAccessor>()?;
let dba = ctx.data::<RtsaDbAccessor>()?;
let paging = dba.query_user_page(page.into(), query.into()).await?;
Ok(paging.into())
}
@ -63,12 +63,12 @@ impl UserMutation {
async fn sync_user(&self, ctx: &Context<'_>) -> async_graphql::Result<bool> {
let http_client = ctx.data::<UserAuthClient>()?;
let users = http_client.query_all_users(ctx.data::<Token>()?).await?;
let dba = ctx.data::<RtssDbAccessor>()?;
let dba = ctx.data::<RtsaDbAccessor>()?;
dba.sync_user(
users
.into_iter()
.map(|u| u.into())
.collect::<Vec<rtss_db::SyncUserInfo>>()
.collect::<Vec<rtsa_db::SyncUserInfo>>()
.as_slice(),
)
.await?;
@ -85,7 +85,7 @@ pub struct UserQueryDto {
pub roles: Option<Vec<Role>>,
}
impl From<UserQueryDto> for rtss_db::UserPageFilter {
impl From<UserQueryDto> for rtsa_db::UserPageFilter {
fn from(value: UserQueryDto) -> Self {
Self {
id: value.id,
@ -122,8 +122,8 @@ impl From<UserInfoDto> for UserDto {
}
}
impl From<rtss_db::model::UserModel> for UserDto {
fn from(value: rtss_db::model::UserModel) -> Self {
impl From<rtsa_db::model::UserModel> for UserDto {
fn from(value: rtsa_db::model::UserModel) -> Self {
Self {
id: value.id,
name: value.username,
@ -147,7 +147,7 @@ impl UserId {
}
}
impl Loader<UserId> for RtssDbLoader {
impl Loader<UserId> for RtsaDbLoader {
type Value = String;
type Error = Arc<DbAccessError>;

View File

@ -0,0 +1,10 @@
/// 数据库加载器
pub struct RtsaDbLoader {
pub(crate) db_accessor: rtsa_db::RtsaDbAccessor,
}
impl RtsaDbLoader {
pub fn new(db_accessor: rtsa_db::RtsaDbAccessor) -> Self {
Self { db_accessor }
}
}

View File

@ -10,12 +10,13 @@ use axum::{
};
use dataloader::DataLoader;
use http::{playground_source, GraphQLPlaygroundConfig};
use rtss_log::tracing::{debug, info};
use rtsa_db::RtsaDbAccessor;
use rtsa_log::tracing::{debug, info};
use tokio::net::TcpListener;
use tower_http::cors::CorsLayer;
use crate::apis::{Mutation, Query};
use crate::loader::RtssDbLoader;
use crate::loader::RtsaDbLoader;
use crate::user_auth;
pub use crate::user_auth::UserAuthClient;
@ -47,7 +48,12 @@ impl ServerConfig {
}
pub async fn serve(config: ServerConfig) -> anyhow::Result<()> {
let schema = new_schema(config.clone()).await;
let client = config
.user_auth_client
.clone()
.expect("user auth client not configured");
let dba = rtsa_db::get_db_accessor(&config.database_url).await;
let schema = new_schema(SchemaOptions::new(client, dba));
let app = Router::new()
.route("/", get(graphiql).post(graphql_handler))
@ -70,7 +76,7 @@ pub async fn serve(config: ServerConfig) -> anyhow::Result<()> {
}
async fn graphql_handler(
State(schema): State<RtssAppSchema>,
State(schema): State<RtsaAppSchema>,
headers: HeaderMap,
req: GraphQLRequest,
) -> GraphQLResponse {
@ -86,20 +92,54 @@ async fn graphiql() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}
pub type RtssAppSchema = Schema<Query, Mutation, EmptySubscription>;
pub type RtsaAppSchema = Schema<Query, Mutation, EmptySubscription>;
pub async fn new_schema(config: ServerConfig) -> RtssAppSchema {
let client = config
.user_auth_client
.expect("user auth client not configured");
let user_info_cache = crate::user_auth::UserAuthCache::new(client.clone());
let dba = rtss_db::get_db_accessor(&config.database_url).await;
let loader = RtssDbLoader::new(dba.clone());
pub struct SchemaOptions {
pub user_auth_client: UserAuthClient,
pub user_info_cache: user_auth::UserAuthCache,
pub rtsa_dba: RtsaDbAccessor,
}
impl SchemaOptions {
pub fn new(user_auth_client: UserAuthClient, rtsa_dba: RtsaDbAccessor) -> Self {
let user_info_cache = user_auth::UserAuthCache::new(user_auth_client.clone());
Self {
user_auth_client,
user_info_cache,
rtsa_dba,
}
}
}
pub fn new_schema(options: SchemaOptions) -> RtsaAppSchema {
let loader = RtsaDbLoader::new(options.rtsa_dba.clone());
Schema::build(Query::default(), Mutation::default(), EmptySubscription)
.data(client)
.data(user_info_cache)
.data(dba)
.data(options.user_auth_client)
.data(options.user_info_cache)
.data(options.rtsa_dba)
.data(DataLoader::new(loader, tokio::spawn))
// .data(MutexSimulationManager::default())
.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_new_schema() {
let dba =
rtsa_db::get_db_accessor("postgresql://joylink:Joylink@0503@localhost:5432/joylink")
.await;
let _ = new_schema(SchemaOptions::new(
crate::UserAuthClient {
base_url: "".to_string(),
login_url: "".to_string(),
logout_url: "".to_string(),
user_info_url: "".to_string(),
sync_user_url: "".to_string(),
},
dba,
));
}
}

View File

@ -70,7 +70,7 @@ mod tests {
#[test]
fn test_jwt() {
rtss_log::Logging::default().init();
rtsa_log::Logging::default().init();
let claim = Claims::new(5);
let jwt = build_jwt(claim).unwrap();
println!("jwt: {}", jwt.0);

View File

@ -6,8 +6,8 @@ use std::{
use async_graphql::Guard;
use axum::http::HeaderMap;
use chrono::{DateTime, Local};
use rtss_dto::common::Role;
use rtss_log::tracing::error;
use rtsa_dto::common::Role;
use rtsa_log::tracing::error;
use serde::{Deserialize, Serialize};
mod jwt_auth;
@ -225,7 +225,7 @@ fn parse_to_date_time(s: &str) -> chrono::DateTime<Local> {
.unwrap()
}
impl From<UserInfoDto> for rtss_db::SyncUserInfo {
impl From<UserInfoDto> for rtsa_db::SyncUserInfo {
fn from(user_info: UserInfoDto) -> Self {
Self {
id: user_info.id_i32(),
@ -276,9 +276,6 @@ impl UserAuthCache {
#[cfg(test)]
mod tests {
use anyhow::Ok;
use rtss_log::tracing::Level;
use super::*;
@ -302,25 +299,25 @@ mod tests {
println!("{:?}", dt);
}
#[tokio::test]
async fn test_user_auth_cache() -> anyhow::Result<()> {
rtss_log::Logging::default().with_level(Level::DEBUG).init();
let client = UserAuthClient {
base_url: "http://192.168.33.233/rtss-server".to_string(),
login_url: "/api/login".to_string(),
logout_url: "/api/login/logout".to_string(),
user_info_url: "/api/login/getUserInfo".to_string(),
sync_user_url: "/api/userinfo/list/all".to_string(),
};
let cache = UserAuthCache::new(client.clone());
let token = cache.client.login(LoginInfo::default()).await?;
let user = cache.query_user(&token).await?;
println!("token: {}, {:?}", token, user);
assert_eq!(cache.len(), 1);
// #[tokio::test]
// async fn test_user_auth_cache() -> anyhow::Result<()> {
// rtsa_log::Logging::default().with_level(Level::DEBUG).init();
// let client = UserAuthClient {
// base_url: "http://192.168.33.233/rtsa-server".to_string(),
// login_url: "/api/login".to_string(),
// logout_url: "/api/login/logout".to_string(),
// user_info_url: "/api/login/getUserInfo".to_string(),
// sync_user_url: "/api/userinfo/list/all".to_string(),
// };
// let cache = UserAuthCache::new(client.clone());
// let token = cache.client.login(LoginInfo::default()).await?;
// let user = cache.query_user(&token).await?;
// println!("token: {}, {:?}", token, user);
// assert_eq!(cache.len(), 1);
let user_list = client.query_all_users(&Token(token)).await?;
println!("{:?}", user_list);
// let user_list = client.query_all_users(&Token(token)).await?;
// println!("{:?}", user_list);
Ok(())
}
// Ok(())
// }
}

View File

@ -20,9 +20,9 @@ pub struct Log {
level: String,
}
impl From<Log> for rtss_log::Logging {
impl From<Log> for rtsa_log::Logging {
fn from(log: Log) -> Self {
rtss_log::Logging {
rtsa_log::Logging {
level: log.level.parse().unwrap(),
..Default::default()
}
@ -69,9 +69,9 @@ impl AppConfig {
// Default to 'dev' env
// Note that this file is _optional_
.add_source(File::with_name(&format!("{dir}/{run_mode}")).required(true))
// Add in settings from the environment (with a prefix of RTSS_SIM)
// Add in settings from the environment (with a prefix of rtsa_SIM)
// Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
.add_source(Environment::with_prefix("RTSS_SIM").separator("_"))
.add_source(Environment::with_prefix("rtsa_SIM").separator("_"))
// You may also programmatically change settings
// .set_override("database.url", "postgres://")?
// build the configuration

View File

@ -1,10 +1,12 @@
use clap::Parser;
use enum_dispatch::enum_dispatch;
use crate::{app_config, db::DbSubCommand, CmdExecutor};
use crate::{app_config, CmdExecutor};
use super::DbSubCommand;
#[derive(Parser, Debug)]
#[command(name = "rtss-sim", version, author, about, long_about = None)]
#[command(name = "rtsa-sim", version, author, about, long_about = None)]
pub struct Cmd {
#[command(subcommand)]
pub cmd: SubCommand,
@ -29,11 +31,11 @@ impl CmdExecutor for ServerOpts {
async fn execute(&self) -> anyhow::Result<()> {
let app_config =
app_config::AppConfig::new(&self.config_path).expect("Failed to load app config");
let log: rtss_log::Logging = app_config.log.into();
let log: rtsa_log::Logging = app_config.log.into();
log.init();
rtss_api::serve(
rtss_api::ServerConfig::new(&app_config.database.url, app_config.server.port)
.with_user_auth_client(rtss_api::UserAuthClient {
rtsa_api::serve(
rtsa_api::ServerConfig::new(&app_config.database.url, app_config.server.port)
.with_user_auth_client(rtsa_api::UserAuthClient {
base_url: app_config.sso.base_url,
login_url: app_config.sso.login_url,
logout_url: app_config.sso.logout_url,

View File

@ -22,6 +22,6 @@ impl CmdExecutor for MigrateOpts {
async fn execute(&self) -> anyhow::Result<()> {
let app_config =
app_config::AppConfig::new(&self.config_path).expect("Failed to load app config");
rtss_db::run_migrations(&app_config.database.url).await
rtsa_db::run_migrations(&app_config.database.url).await
}
}

View File

@ -1,4 +1,3 @@
mod app_config;
mod cmd;
mod db;

4
manager/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
mod app_config;
mod commands;
pub use commands::*;

View File

@ -1,5 +1,5 @@
use clap::Parser;
use rtss_simulation::{Cmd, CmdExecutor};
use manager::{Cmd, CmdExecutor};
#[tokio::main]
async fn main() -> anyhow::Result<()> {

View File

@ -1 +1 @@
DROP SCHEMA rtss CASCADE;
DROP SCHEMA rtsa CASCADE;

View File

@ -1,9 +1,12 @@
-- 初始化数据库SCHEMA(所有轨道交通信号系统仿真的表、类型等都在rtss SCHEMA下)
CREATE SCHEMA rtss;
-- 初始化数据库SCHEMA(所有轨道交通信号系统仿真的表、类型等都在rtsa SCHEMA下)
CREATE SCHEMA rtsa;
-- 创建mqtt客户端id序列
CREATE SEQUENCE rtsa.mqtt_client_id_seq;
-- 创建用户表
CREATE TABLE
rtss.user (
rtsa.user (
id SERIAL PRIMARY KEY, -- id 自增主键
username VARCHAR(128) NOT NULL, -- 用户名
password VARCHAR(128) NOT NULL, -- 密码
@ -15,40 +18,40 @@ CREATE TABLE
);
-- 创建用户名称索引
CREATE INDEX ON rtss.user (username);
CREATE INDEX ON rtsa.user (username);
-- 创建用户邮箱索引
CREATE INDEX ON rtss.user (email);
CREATE INDEX ON rtsa.user (email);
-- 创建用户手机号索引
CREATE INDEX ON rtss.user (mobile);
CREATE INDEX ON rtsa.user (mobile);
-- 创建用户角色索引
CREATE INDEX ON rtss.user USING GIN (roles);
CREATE INDEX ON rtsa.user USING GIN (roles);
-- 注释用户表
COMMENT ON TABLE rtss.user IS '用户表';
COMMENT ON TABLE rtsa.user IS '用户表';
-- 注释用户表字段
COMMENT ON COLUMN rtss.user.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.user.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.user.username IS '用户名';
COMMENT ON COLUMN rtsa.user.username IS '用户名';
COMMENT ON COLUMN rtss.user.password IS '密码';
COMMENT ON COLUMN rtsa.user.password IS '密码';
COMMENT ON COLUMN rtss.user.email IS '邮箱';
COMMENT ON COLUMN rtsa.user.email IS '邮箱';
COMMENT ON COLUMN rtss.user.mobile IS '手机号';
COMMENT ON COLUMN rtsa.user.mobile IS '手机号';
COMMENT ON COLUMN rtss.user.roles IS '角色列表';
COMMENT ON COLUMN rtsa.user.roles IS '角色列表';
COMMENT ON COLUMN rtss.user.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.user.created_at IS '创建时间';
COMMENT ON COLUMN rtss.user.updated_at IS '更新时间';
COMMENT ON COLUMN rtsa.user.updated_at IS '更新时间';
-- 创建草稿数据表
CREATE TABLE
rtss.draft_data (
rtsa.draft_data (
id SERIAL PRIMARY KEY, -- id 自增主键
name VARCHAR(128) NOT NULL, -- 草稿名称
data_type INT NOT NULL, -- 数据类型
@ -59,44 +62,44 @@ CREATE TABLE
is_shared BOOLEAN NOT NULL DEFAULT FALSE, -- 是否共享
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (user_id) REFERENCES rtsa.user (id) ON DELETE CASCADE, -- 用户外键
UNIQUE (name, data_type, user_id) -- 一个用户的某个类型的草稿名称唯一
);
-- 创建草稿数据用户索引
CREATE INDEX ON rtss.draft_data (user_id);
CREATE INDEX ON rtsa.draft_data (user_id);
-- 创建草稿数据类型索引
CREATE INDEX ON rtss.draft_data (data_type);
CREATE INDEX ON rtsa.draft_data (data_type);
-- 创建草稿数据配置项索引
CREATE INDEX ON rtss.draft_data USING GIN (options);
CREATE INDEX ON rtsa.draft_data USING GIN (options);
-- 注释草稿数据表
COMMENT ON TABLE rtss.draft_data IS '草稿数据表';
COMMENT ON TABLE rtsa.draft_data IS '草稿数据表';
-- 注释草稿数据表字段
COMMENT ON COLUMN rtss.draft_data.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.draft_data.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.draft_data.name IS '草稿名称';
COMMENT ON COLUMN rtsa.draft_data.name IS '草稿名称';
COMMENT ON COLUMN rtss.draft_data.data_type IS '数据类型';
COMMENT ON COLUMN rtsa.draft_data.data_type IS '数据类型';
COMMENT ON COLUMN rtss.draft_data.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtsa.draft_data.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtss.draft_data.data IS '草稿数据';
COMMENT ON COLUMN rtsa.draft_data.data IS '草稿数据';
COMMENT ON COLUMN rtss.draft_data.user_id IS '创建用户id';
COMMENT ON COLUMN rtsa.draft_data.user_id IS '创建用户id';
COMMENT ON COLUMN rtss.draft_data.is_shared IS '是否共享';
COMMENT ON COLUMN rtsa.draft_data.is_shared IS '是否共享';
COMMENT ON COLUMN rtss.draft_data.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.draft_data.created_at IS '创建时间';
COMMENT ON COLUMN rtss.draft_data.updated_at IS '更新时间';
COMMENT ON COLUMN rtsa.draft_data.updated_at IS '更新时间';
-- 创建发布数据表
CREATE TABLE
rtss.release_data (
rtsa.release_data (
id SERIAL PRIMARY KEY, -- id 自增主键
name VARCHAR(128) NOT NULL, -- 发布数据名称(数据唯一标识)
data_type INT NOT NULL, -- 数据类型
@ -106,47 +109,47 @@ CREATE TABLE
is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (user_id) REFERENCES rtsa.user (id) ON DELETE CASCADE, -- 用户外键
UNIQUE(data_type, name) -- 数据类型和名称唯一
);
-- 创建发布数据名称索引
CREATE INDEX ON rtss.release_data (name);
CREATE INDEX ON rtsa.release_data (name);
-- 创建发布数据用户索引
CREATE INDEX ON rtss.release_data (user_id);
CREATE INDEX ON rtsa.release_data (user_id);
-- 创建发布数据类型索引
CREATE INDEX ON rtss.release_data (data_type);
CREATE INDEX ON rtsa.release_data (data_type);
-- 创建发布数据配置项索引
CREATE INDEX ON rtss.release_data USING GIN (options);
CREATE INDEX ON rtsa.release_data USING GIN (options);
-- 注释发布数据表
COMMENT ON TABLE rtss.release_data IS '发布数据表';
COMMENT ON TABLE rtsa.release_data IS '发布数据表';
-- 注释发布数据表字段
COMMENT ON COLUMN rtss.release_data.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.release_data.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.release_data.name IS '发布数据名称(数据唯一标识)';
COMMENT ON COLUMN rtsa.release_data.name IS '发布数据名称(数据唯一标识)';
COMMENT ON COLUMN rtss.release_data.data_type IS '数据类型';
COMMENT ON COLUMN rtsa.release_data.data_type IS '数据类型';
COMMENT ON COLUMN rtss.release_data.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtsa.release_data.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtss.release_data.used_version_id IS '使用的版本数据id';
COMMENT ON COLUMN rtsa.release_data.used_version_id IS '使用的版本数据id';
COMMENT ON COLUMN rtss.release_data.user_id IS '发布/更新用户id';
COMMENT ON COLUMN rtsa.release_data.user_id IS '发布/更新用户id';
COMMENT ON COLUMN rtss.release_data.is_published IS '是否上架';
COMMENT ON COLUMN rtsa.release_data.is_published IS '是否上架';
COMMENT ON COLUMN rtss.release_data.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.release_data.created_at IS '创建时间';
COMMENT ON COLUMN rtss.release_data.updated_at IS '更新时间';
COMMENT ON COLUMN rtsa.release_data.updated_at IS '更新时间';
-- 创建发布数据版本表
CREATE TABLE
rtss.release_data_version (
rtsa.release_data_version (
id SERIAL PRIMARY KEY, -- id 自增主键
release_data_id INT NOT NULL, -- 发布数据id
options JSONB NULL, -- 数据相关的参数项或配置项
@ -154,46 +157,46 @@ CREATE TABLE
description TEXT NOT NULL, -- 版本描述
user_id INT NOT NULL, -- 发布用户id
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (release_data_id) REFERENCES rtss.release_data (id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES rtsa.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (release_data_id) REFERENCES rtsa.release_data (id) ON DELETE CASCADE
);
-- 创建发布数据版本发布数据索引
CREATE INDEX ON rtss.release_data_version (release_data_id);
CREATE INDEX ON rtsa.release_data_version (release_data_id);
-- 创建发布数据版本用户索引
CREATE INDEX ON rtss.release_data_version (user_id);
CREATE INDEX ON rtsa.release_data_version (user_id);
-- 创建发布数据版本配置项索引
CREATE INDEX ON rtss.release_data_version USING GIN (options);
CREATE INDEX ON rtsa.release_data_version USING GIN (options);
-- 创建发布数据当前版本外键
ALTER TABLE rtss.release_data ADD FOREIGN KEY (used_version_id) REFERENCES rtss.release_data_version (id) ON DELETE SET NULL;
ALTER TABLE rtsa.release_data ADD FOREIGN KEY (used_version_id) REFERENCES rtsa.release_data_version (id) ON DELETE SET NULL;
-- 创建草稿数据默认发布数据外键
ALTER TABLE rtss.draft_data ADD FOREIGN KEY (default_release_data_id) REFERENCES rtss.release_data (id) ON DELETE SET NULL;
ALTER TABLE rtsa.draft_data ADD FOREIGN KEY (default_release_data_id) REFERENCES rtsa.release_data (id) ON DELETE SET NULL;
-- 注释发布数据版本表
COMMENT ON TABLE rtss.release_data_version IS '发布数据版本表';
COMMENT ON TABLE rtsa.release_data_version IS '发布数据版本表';
-- 注释发布数据版本表字段
COMMENT ON COLUMN rtss.release_data_version.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.release_data_version.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.release_data_version.release_data_id IS '发布数据id';
COMMENT ON COLUMN rtsa.release_data_version.release_data_id IS '发布数据id';
COMMENT ON COLUMN rtss.release_data_version.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtsa.release_data_version.options IS '数据相关的参数项或配置项';
COMMENT ON COLUMN rtss.release_data_version.data IS '数据';
COMMENT ON COLUMN rtsa.release_data_version.data IS '数据';
COMMENT ON COLUMN rtss.release_data_version.description IS '版本描述';
COMMENT ON COLUMN rtsa.release_data_version.description IS '版本描述';
COMMENT ON COLUMN rtss.release_data_version.user_id IS '发布用户id';
COMMENT ON COLUMN rtsa.release_data_version.user_id IS '发布用户id';
COMMENT ON COLUMN rtss.release_data_version.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.release_data_version.created_at IS '创建时间';
-- 创建feature表
CREATE TABLE
rtss.feature (
rtsa.feature (
id SERIAL PRIMARY KEY, -- id 自增主键
feature_type INT NOT NULL, -- feature类型
name VARCHAR(128) NOT NULL UNIQUE, -- feature名称
@ -204,68 +207,68 @@ CREATE TABLE
updater_id INT NOT NULL, -- 更新用户id
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
FOREIGN KEY (creator_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (updater_id) REFERENCES rtss.user (id) ON DELETE CASCADE -- 用户外键
FOREIGN KEY (creator_id) REFERENCES rtsa.user (id) ON DELETE CASCADE, -- 用户外键
FOREIGN KEY (updater_id) REFERENCES rtsa.user (id) ON DELETE CASCADE -- 用户外键
);
-- 创建feature类型索引
CREATE INDEX ON rtss.feature (feature_type);
CREATE INDEX ON rtsa.feature (feature_type);
-- 创建feature名称索引
CREATE INDEX ON rtss.feature (name);
CREATE INDEX ON rtsa.feature (name);
-- 注释仿真feature表
COMMENT ON TABLE rtss.feature IS 'feature表';
COMMENT ON TABLE rtsa.feature IS 'feature表';
-- 注释仿真feature表字段
COMMENT ON COLUMN rtss.feature.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.feature.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.feature.feature_type IS 'feature类型';
COMMENT ON COLUMN rtsa.feature.feature_type IS 'feature类型';
COMMENT ON COLUMN rtss.feature.name IS 'feature名称';
COMMENT ON COLUMN rtsa.feature.name IS 'feature名称';
COMMENT ON COLUMN rtss.feature.description IS 'feature描述';
COMMENT ON COLUMN rtsa.feature.description IS 'feature描述';
COMMENT ON COLUMN rtss.feature.config IS 'feature配置';
COMMENT ON COLUMN rtsa.feature.config IS 'feature配置';
COMMENT ON COLUMN rtss.feature.is_published IS '是否上架';
COMMENT ON COLUMN rtsa.feature.is_published IS '是否上架';
COMMENT ON COLUMN rtss.feature.creator_id IS '创建用户id';
COMMENT ON COLUMN rtsa.feature.creator_id IS '创建用户id';
COMMENT ON COLUMN rtss.feature.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.feature.created_at IS '创建时间';
COMMENT ON COLUMN rtss.feature.updated_at IS '更新时间';
COMMENT ON COLUMN rtsa.feature.updated_at IS '更新时间';
-- 创建用户配置表
CREATE TABLE
rtss.user_config (
rtsa.user_config (
id SERIAL PRIMARY KEY, -- id 自增主键
user_id INT NOT NULL, -- 用户id
config_type INT NOT NULL, -- 配置类型
config BYTEA NOT NULL, -- 配置
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE -- 用户外键
FOREIGN KEY (user_id) REFERENCES rtsa.user (id) ON DELETE CASCADE -- 用户外键
);
-- 创建用户配置用户索引
CREATE INDEX ON rtss.user_config (user_id);
CREATE INDEX ON rtsa.user_config (user_id);
-- 创建用户配置类型索引
CREATE INDEX ON rtss.user_config (config_type);
CREATE INDEX ON rtsa.user_config (config_type);
-- 注释用户feature配置表
COMMENT ON TABLE rtss.user_config IS '用户feature配置表';
COMMENT ON TABLE rtsa.user_config IS '用户feature配置表';
-- 注释用户feature配置表字段
COMMENT ON COLUMN rtss.user_config.id IS 'id 自增主键';
COMMENT ON COLUMN rtsa.user_config.id IS 'id 自增主键';
COMMENT ON COLUMN rtss.user_config.user_id IS '用户id';
COMMENT ON COLUMN rtsa.user_config.user_id IS '用户id';
COMMENT ON COLUMN rtss.user_config.config_type IS '配置类型';
COMMENT ON COLUMN rtsa.user_config.config_type IS '配置类型';
COMMENT ON COLUMN rtss.user_config.config IS '配置';
COMMENT ON COLUMN rtsa.user_config.config IS '配置';
COMMENT ON COLUMN rtss.user_config.created_at IS '创建时间';
COMMENT ON COLUMN rtsa.user_config.created_at IS '创建时间';
COMMENT ON COLUMN rtss.user_config.updated_at IS '更新时间';
COMMENT ON COLUMN rtsa.user_config.updated_at IS '更新时间';

1
rtsa-proto-msg Submodule

@ -0,0 +1 @@
Subproject commit 1f53057b3f87790ef27c91399a5bb7e890f05549

@ -1 +0,0 @@
Subproject commit 02d9ad3b44876fc470e460bb6975c3d08f698b1b

24
simulation/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "simulation"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy_core = { workspace = true }
bevy_ecs = { workspace = true }
bevy_app = { workspace = true }
bevy_time = { workspace = true }
rayon = { workspace = true }
thiserror = { workspace = true }
lazy_static = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
config = { workspace = true }
clap = { workspace = true, features = ["derive"] }
enum_dispatch = { workspace = true }
anyhow = { workspace = true }
rtsa_log = { path = "../crates/rtsa_log" }
rtsa_dto = { path = "../crates/rtsa_dto" }
rtsa_db = { path = "../crates/rtsa_db" }
rtsa_mqtt = { path = "../crates/rtsa_mqtt" }

View File

@ -0,0 +1,9 @@
[database]
[log]
level = "debug"
[mqtt]
url = "http://localhost:8080"
username = "admin"
password = "admin"

7
simulation/conf/dev.toml Normal file
View File

@ -0,0 +1,7 @@
[database]
url = "postgresql://joylink:Joylink@0503@localhost:5432/joylink"
[mqtt]
url = "tcp://localhost:1883"
username = "rtsa"
password = "Joylink@0503"

View File

@ -0,0 +1,10 @@
[database]
url = "postgresql://joylink:Joylink@0503@10.11.11.2:5432/joylink"
[log]
level = "debug"
[mqtt]
url = "tcp://192.168.3.233:1883"
username = "rtsa"
password = "Joylink@0503"

View File

@ -0,0 +1,87 @@
use std::env;
use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Database {
pub url: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Log {
level: String,
}
impl From<Log> for rtsa_log::Logging {
fn from(log: Log) -> Self {
rtsa_log::Logging {
level: log.level.parse().unwrap(),
..Default::default()
}
}
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Mqtt {
pub url: String,
pub username: String,
pub password: String,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct AppConfig {
pub log: Log,
pub database: Database,
pub mqtt: Mqtt,
}
impl AppConfig {
pub fn new(dir: &str) -> Result<Self, ConfigError> {
let run_mode = env::var("RUN_MODE")
.unwrap_or_else(|_| "dev".into())
.trim()
.to_string();
println!("RUN_MODE: {}", run_mode);
// log 当前目录
// println!("Current dir: {:?}", std::env::current_dir().unwrap());
let s = Config::builder()
// Start off by merging in the "default" configuration file
.add_source(File::with_name(&format!("{dir}/default")))
// Add in a local configuration file
// This file shouldn't be checked in to git
.add_source(File::with_name(&format!("{dir}/local")).required(false))
// Add in the current environment file
// Default to 'dev' env
// Note that this file is _optional_
.add_source(File::with_name(&format!("{dir}/{run_mode}")).required(true))
// Add in settings from the environment (with a prefix of rtsa_SIM)
// Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
.add_source(Environment::with_prefix("rtsa_SIM").separator("_"))
// You may also programmatically change settings
// .set_override("database.url", "postgres://")?
// build the configuration
.build()?;
// You can deserialize (and thus freeze) the entire configuration as
s.try_deserialize()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_config() {
std::env::set_var("RUN_MODE", "local_test");
let config = AppConfig::new("conf").unwrap();
println!("{:?}", config);
}
}

View File

@ -0,0 +1,48 @@
use clap::Parser;
use enum_dispatch::enum_dispatch;
use crate::app_config;
use super::{CmdExecutor, DbSubCommand};
#[derive(Parser, Debug)]
#[command(name = "rtsa-sim", version, author, about, long_about = None)]
pub struct Cmd {
#[command(subcommand)]
pub cmd: SubCommand,
}
#[derive(Parser, Debug)]
#[enum_dispatch(CmdExecutor)]
pub enum SubCommand {
#[command(name = "serve")]
Serve(ServerOpts),
#[command(name = "db", subcommand, about = "Database operations")]
Db(DbSubCommand),
}
#[derive(Parser, Debug)]
pub struct ServerOpts {
#[clap(short, long, required = false, default_value = "conf")]
config_path: String,
}
impl CmdExecutor for ServerOpts {
async fn execute(&self) -> anyhow::Result<()> {
let app_config =
app_config::AppConfig::new(&self.config_path).expect("Failed to load app config");
let log: rtsa_log::Logging = app_config.log.into();
log.init();
// 数据库访问器初始化
rtsa_db::init_default_db_accessor(&app_config.database.url).await;
// mqtt客户端初始化
let cli_id = rtsa_db::get_default_db_accessor()
.get_next_mqtt_client_id()
.await?;
let mqtt_cli_options =
rtsa_mqtt::MqttClientOptions::new(&format!("rtsa{}", cli_id), &app_config.mqtt.url)
.set_credentials(&app_config.mqtt.username, &app_config.mqtt.password);
rtsa_mqtt::init_global_mqtt_client(mqtt_cli_options).await?;
Ok(())
}
}

View File

@ -0,0 +1,29 @@
use clap::Parser;
use enum_dispatch::enum_dispatch;
use crate::app_config;
use super::CmdExecutor;
#[derive(Parser, Debug)]
#[enum_dispatch(CmdExecutor)]
pub enum DbSubCommand {
#[command(name = "migrate", about = "Migrate database")]
Migrate(MigrateOpts),
}
#[derive(Parser, Debug)]
pub struct MigrateOpts {
#[clap(long, required = false, default_value = "conf")]
config_path: String,
#[clap(long, required = false, default_value = "migrations")]
file_path: String,
}
impl CmdExecutor for MigrateOpts {
async fn execute(&self) -> anyhow::Result<()> {
let app_config =
app_config::AppConfig::new(&self.config_path).expect("Failed to load app config");
rtsa_db::run_migrations(&app_config.database.url).await
}
}

View File

@ -0,0 +1,12 @@
mod cmd;
mod db;
pub use cmd::*;
use db::*;
use enum_dispatch::enum_dispatch;
#[allow(async_fn_in_trait)]
#[enum_dispatch]
pub trait CmdExecutor {
async fn execute(&self) -> anyhow::Result<()>;
}

View File

@ -0,0 +1,93 @@
//! 顶级通用组件
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use bevy_ecs::{component::Component, entity::Entity, system::Resource};
use tokio::sync::broadcast;
#[derive(Resource, Clone, Debug)]
pub struct SimulationInfo {
pub id: String,
pub feature_id: String,
pub is_paused: bool,
pub speed: f32,
}
impl SimulationInfo {
pub fn new(id: &str, feature_id: &str) -> Self {
SimulationInfo {
id: id.to_string(),
feature_id: feature_id.to_string(),
is_paused: false,
speed: 1.0,
}
}
}
// 设备编号组件
#[derive(Component, Debug, Clone, PartialEq, Eq)]
pub struct Uid(pub String);
impl Default for Uid {
fn default() -> Self {
Uid("".to_string())
}
}
/// 仿真uid与实体映射资源
#[derive(Resource, Clone, Debug, Default)]
pub struct SimulationUidEntityMapResource(pub Arc<Mutex<HashMap<String, Entity>>>);
impl SimulationUidEntityMapResource {
pub fn get_entity(&self, uid: &str) -> Option<Entity> {
self.0.lock().unwrap().get(uid).cloned()
}
pub fn insert_entity(&self, uid: String, entity: Entity) {
self.0.lock().unwrap().insert(uid, entity);
}
}
#[derive(Resource, Debug)]
pub struct TxResource {
tx: broadcast::Sender<rtsa_dto::simulation::Operation>,
rx: Option<broadcast::Receiver<rtsa_dto::simulation::Operation>>,
}
impl TxResource {
pub fn new(capacity: usize) -> Self {
let (tx, rx) = broadcast::channel(capacity);
TxResource { tx, rx: Some(rx) }
}
pub fn get_tx(&self) -> broadcast::Sender<rtsa_dto::simulation::Operation> {
self.tx.clone()
}
pub fn subscribe(&mut self) -> broadcast::Receiver<rtsa_dto::simulation::Operation> {
let rx = self.tx.subscribe();
if self.rx.is_some() {
std::mem::take(&mut self.rx);
}
rx
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::world;
use super::*;
#[test]
fn it_works() {
let simulation_resource = SimulationUidEntityMapResource::default();
let mut world = world::World::default();
let uid = Uid("1".to_string());
let entity = world.spawn(uid.clone()).id();
simulation_resource.insert_entity(uid.clone().0, entity);
assert_eq!(simulation_resource.get_entity(&uid.0), Some(entity));
}
}

9
simulation/src/error.rs Normal file
View File

@ -0,0 +1,9 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum SimulationError {
#[error("未知的仿真错误")]
Unknown,
#[error("创建错误: {0}")]
CreateError(String),
}

6
simulation/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod app_config;
pub mod commands;
pub mod components;
pub mod error;
pub mod manage;
pub mod modules;

9
simulation/src/main.rs Normal file
View File

@ -0,0 +1,9 @@
use clap::Parser;
use simulation::commands::{Cmd, CmdExecutor};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cmd = Cmd::parse();
cmd.cmd.execute().await?;
Ok(())
}

View File

@ -0,0 +1,18 @@
use bevy_app::{Plugin, Startup};
use bevy_ecs::system::Res;
use rtsa_log::tracing::debug;
use crate::components::SimulationInfo;
#[derive(Default)]
pub struct DataLoadingPlugin;
impl Plugin for DataLoadingPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_systems(Startup, loading);
}
}
fn loading(info: Res<SimulationInfo>) {
debug!("Loading data: {:?}", info);
}

View File

@ -0,0 +1,5 @@
mod data_loading_plugin;
mod simulation;
mod simulation_control_plugin;
pub use simulation::{Simulation, SimulationOptions};

View File

@ -0,0 +1,157 @@
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use bevy_app::{prelude::*, MainSchedulePlugin};
use bevy_ecs::{
event::{event_update_system, EventRegistry},
prelude::*,
reflect::AppTypeRegistry,
schedule::ScheduleLabel,
system::ResMut,
};
use bevy_time::{Fixed, Time, TimePlugin, Virtual};
use rtsa_log::tracing::{debug, error, info};
use tokio::sync::broadcast;
use crate::{
components::{SimulationInfo, SimulationUidEntityMapResource, TxResource},
error::SimulationError,
};
use super::{
data_loading_plugin::DataLoadingPlugin, simulation_control_plugin::SimulationControlPlugin,
};
/// 仿真构造配置项
pub struct SimulationOptions {
/// 仿真ID
pub(crate) id: String,
/// 仿真功能特性类型
pub(crate) feature_id: String,
/// 仿真主逻辑循环间隔,详细请查看 [`Time<Fixed>`](bevy_time::fixed::Fixed)
pub(crate) loop_duration: Duration,
}
impl SimulationOptions {
pub fn new(id: &str, feature_id: &str) -> Self {
SimulationOptions {
id: id.to_string(),
feature_id: feature_id.to_string(),
loop_duration: Duration::from_millis(20),
}
}
pub fn loop_duration(mut self, loop_duration: Duration) -> Self {
self.loop_duration = loop_duration;
self
}
}
#[derive(Debug, Clone)]
pub struct Simulation {
app: Arc<Mutex<SubApp>>,
tx: broadcast::Sender<rtsa_dto::simulation::Operation>,
}
impl Simulation {
pub fn new(options: SimulationOptions) -> Result<Self, SimulationError> {
let mut sub = SubApp::new();
sub.init_resource::<AppTypeRegistry>()
.init_resource::<EventRegistry>();
sub.update_schedule = Some(Main.intern());
sub.add_plugins(MainSchedulePlugin).add_plugins(TimePlugin);
sub.add_systems(
First,
event_update_system
.in_set(bevy_ecs::event::EventUpdates)
.run_if(bevy_ecs::event::event_update_condition),
);
sub.add_systems(
PreStartup,
move |mut tv: ResMut<Time<Virtual>>, mut tf: ResMut<Time<Fixed>>| {
debug!("PreStartup set time");
tv.set_max_delta(options.loop_duration);
tf.set_timestep(options.loop_duration);
},
);
// 添加全局共享资源
let tx_resource = TxResource::new(100);
let tx = tx_resource.get_tx();
sub.insert_resource(SimulationInfo::new(&options.id, &options.feature_id))
.insert_resource(SimulationUidEntityMapResource::default())
.insert_resource(tx_resource);
// 添加通用插件
sub.add_plugins(SimulationControlPlugin)
.add_plugins(DataLoadingPlugin);
sub.add_systems(FixedUpdate, hello_world_system);
Ok(Simulation {
app: Arc::new(Mutex::new(sub)),
tx,
})
}
pub fn send_operation(&self, op: rtsa_dto::simulation::Operation) {
if let Err(e) = self.tx.send(op) {
error!("send operation error: {}", e);
}
}
pub fn update(&self) {
let mut app = self.app.lock().unwrap();
app.update();
}
}
fn hello_world_system(tv: Res<Time<Virtual>>) {
info!("hello world: {}", tv.relative_speed());
}
#[cfg(test)]
mod tests {
use std::thread::{self, sleep};
use super::*;
use rtsa_dto::simulation::OperationType;
use rtsa_log::tracing::Level;
#[test]
fn test_new_simulation() {
rtsa_log::Logging::default().with_level(Level::DEBUG).init();
let simulation1 = Simulation::new(SimulationOptions::new("1", "1")).unwrap();
assert!(simulation1.app.lock().unwrap().update_schedule.is_some());
simulation1.send_operation(rtsa_dto::simulation::Operation {
otype: rtsa_dto::simulation::OperationType::SetSpeed as i32,
param: Some(rtsa_dto::simulation::operation::Param::SetSpeedParam(
rtsa_dto::simulation::SetSpeedParam { speed: 2.0 },
)),
});
let clone2 = simulation1.clone();
thread::spawn(move || {
sleep(Duration::from_millis(100));
info!("send pause");
clone2.send_operation(rtsa_dto::simulation::Operation {
otype: OperationType::Pause as i32,
param: None,
});
sleep(Duration::from_millis(200));
info!("send unpause");
clone2.send_operation(rtsa_dto::simulation::Operation {
otype: OperationType::Unpause as i32,
param: None,
});
});
// let simulation2 = Simulation::new(SimulationOptions::new("2", "1")).unwrap();
for _ in 0..30 {
sleep(Duration::from_millis(20));
simulation1.update();
// simulation2.update();
}
}
}

View File

@ -0,0 +1,61 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_time::prelude::*;
use rtsa_dto::simulation::{operation, OperationType, SetSpeedParam};
use rtsa_log::tracing::debug;
use tokio::sync::broadcast;
use crate::components::{SimulationInfo, TxResource};
#[derive(Default)]
pub struct SimulationControlPlugin;
impl Plugin for SimulationControlPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_systems(Startup, init_rx_resource)
.add_systems(Update, handle_control_operation);
}
}
fn init_rx_resource(mut commands: Commands, mut tx: ResMut<TxResource>) {
debug!("init_rx_resource");
commands.insert_resource(RxResource::new(tx.subscribe()));
}
#[derive(Resource, Debug)]
struct RxResource(pub broadcast::Receiver<rtsa_dto::simulation::Operation>);
impl RxResource {
fn new(rx: broadcast::Receiver<rtsa_dto::simulation::Operation>) -> Self {
RxResource(rx)
}
}
fn handle_control_operation(
mut rx: ResMut<RxResource>,
mut time: ResMut<Time<Virtual>>,
info: Res<SimulationInfo>,
) {
if let Ok(op) = rx.0.try_recv() {
match OperationType::try_from(op.otype) {
Ok(OperationType::Pause) => {
debug!("Pausing simulation, id={}", info.id);
time.pause();
}
Ok(OperationType::Unpause) => {
debug!("Unpausing simulation, id={}", info.id);
time.unpause();
}
Ok(OperationType::SetSpeed) => {
if let Some(operation::Param::SetSpeedParam(SetSpeedParam { speed })) = op.param {
debug!("Update simulation id={} speed to {}", info.id, speed);
time.set_relative_speed(speed);
}
}
Ok(OperationType::Destroy) => {
debug!("Exiting simulation, id={}", info.id);
}
_ => {}
}
}
}

View File

@ -0,0 +1,3 @@
mod plugin;
pub use plugin::CiPlugin;

View File

@ -0,0 +1,10 @@
use bevy_app::prelude::*;
#[derive(Default)]
pub struct CiPlugin;
impl Plugin for CiPlugin {
fn build(&self, _app: &mut bevy_app::App) {
todo!()
}
}

View File

@ -0,0 +1,2 @@
pub mod ci;
pub mod trackside;

View File

@ -1,5 +1,6 @@
use bevy_ecs::bundle::Bundle;
use rtss_common::Uid;
use crate::components::Uid;
use super::{PsdState, TurnoutState, TwoNormalPositionsTransform};

Some files were not shown because too many files have changed in this diff Show More