SCLP
This commit is contained in:
parent
641c34b1b3
commit
16c005556f
24
SCLP/Pipfile
Normal file
24
SCLP/Pipfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://mirrors.ustc.edu.cn/pypi/simple"
|
||||||
|
verify_ssl = false
|
||||||
|
name = "pip_conf_index_global"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
fastapi = "*"
|
||||||
|
uvicorn = "*"
|
||||||
|
argparse = "*"
|
||||||
|
pillow = "*"
|
||||||
|
paho-mqtt = "*"
|
||||||
|
requests = "*"
|
||||||
|
pandas = "*"
|
||||||
|
openpyxl = "*"
|
||||||
|
xlrd = "*"
|
||||||
|
aiofiles = "*"
|
||||||
|
psutil = "*"
|
||||||
|
python-multipart = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.10"
|
||||||
|
python_full_version = "3.10.14"
|
||||||
685
SCLP/Pipfile.lock
generated
Normal file
685
SCLP/Pipfile.lock
generated
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "44345757f24b73dd5c0974ed045d2479e1773441b6d789525398bd83e837bfb9"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_full_version": "3.10.14",
|
||||||
|
"python_version": "3.10"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pip_conf_index_global",
|
||||||
|
"url": "https://mirrors.ustc.edu.cn/pypi/simple",
|
||||||
|
"verify_ssl": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"aiofiles": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c",
|
||||||
|
"sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==24.1.0"
|
||||||
|
},
|
||||||
|
"annotated-types": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53",
|
||||||
|
"sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.7.0"
|
||||||
|
},
|
||||||
|
"anyio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94",
|
||||||
|
"sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==4.4.0"
|
||||||
|
},
|
||||||
|
"argparse": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
|
||||||
|
"sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"version": "==1.4.0"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
|
||||||
|
"sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==2024.8.30"
|
||||||
|
},
|
||||||
|
"charset-normalizer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
|
||||||
|
"sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
|
||||||
|
"sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
|
||||||
|
"sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
|
||||||
|
"sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
|
||||||
|
"sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
|
||||||
|
"sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
|
||||||
|
"sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
|
||||||
|
"sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
|
||||||
|
"sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
|
||||||
|
"sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
|
||||||
|
"sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
|
||||||
|
"sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
|
||||||
|
"sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
|
||||||
|
"sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
|
||||||
|
"sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
|
||||||
|
"sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
|
||||||
|
"sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
|
||||||
|
"sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
|
||||||
|
"sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
|
||||||
|
"sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
|
||||||
|
"sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
|
||||||
|
"sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
|
||||||
|
"sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
|
||||||
|
"sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
|
||||||
|
"sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
|
||||||
|
"sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
|
||||||
|
"sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
|
||||||
|
"sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
|
||||||
|
"sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
|
||||||
|
"sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
|
||||||
|
"sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
|
||||||
|
"sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
|
||||||
|
"sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
|
||||||
|
"sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
|
||||||
|
"sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
|
||||||
|
"sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
|
||||||
|
"sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
|
||||||
|
"sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
|
||||||
|
"sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
|
||||||
|
"sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
|
||||||
|
"sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
|
||||||
|
"sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
|
||||||
|
"sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
|
||||||
|
"sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
|
||||||
|
"sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
|
||||||
|
"sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
|
||||||
|
"sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
|
||||||
|
"sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
|
||||||
|
"sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
|
||||||
|
"sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
|
||||||
|
"sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
|
||||||
|
"sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
|
||||||
|
"sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
|
||||||
|
"sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
|
||||||
|
"sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
|
||||||
|
"sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
|
||||||
|
"sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
|
||||||
|
"sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
|
||||||
|
"sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
|
||||||
|
"sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
|
||||||
|
"sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
|
||||||
|
"sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
|
||||||
|
"sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
|
||||||
|
"sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
|
||||||
|
"sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
|
||||||
|
"sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
|
||||||
|
"sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
|
||||||
|
"sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
|
||||||
|
"sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
|
||||||
|
"sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
|
||||||
|
"sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
|
||||||
|
"sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
|
||||||
|
"sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
|
||||||
|
"sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
|
||||||
|
"sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
|
||||||
|
"sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
|
||||||
|
"sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
|
||||||
|
"sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
|
||||||
|
"sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
|
||||||
|
"sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
|
||||||
|
"sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
|
||||||
|
"sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
|
||||||
|
"sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
|
||||||
|
"sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
|
||||||
|
"sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
|
||||||
|
"sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
|
||||||
|
"sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
|
||||||
|
"sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
|
||||||
|
"sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.7.0'",
|
||||||
|
"version": "==3.3.2"
|
||||||
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
|
||||||
|
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==8.1.7"
|
||||||
|
},
|
||||||
|
"et-xmlfile": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c",
|
||||||
|
"sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
|
"exceptiongroup": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
|
||||||
|
"sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==1.2.2"
|
||||||
|
},
|
||||||
|
"fastapi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:3d4729c038414d5193840706907a41839d839523da6ed0c2811f1168cac1798c",
|
||||||
|
"sha256:db84b470bd0e2b1075942231e90e3577e12a903c4dc8696f0d206a7904a7af1c"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.112.2"
|
||||||
|
},
|
||||||
|
"h11": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
|
||||||
|
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==0.14.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac",
|
||||||
|
"sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==3.8"
|
||||||
|
},
|
||||||
|
"numpy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06156c55771da4952a2432aa457cd96159675dcab4336f5307bff042535cb6ea",
|
||||||
|
"sha256:06d14d20b7e98c8c06bb62e56f2b64747dd10c422bb8adbf1e6dd82cd8442e12",
|
||||||
|
"sha256:0816fd52956e14551d8d71319d4b4fcfa1bcb21641f2c603f4eb64c65b1e1009",
|
||||||
|
"sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b",
|
||||||
|
"sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911",
|
||||||
|
"sha256:0a5a25ab780b8c29e443824abefc6ca79047ceeb889a6f76d7b1953649498e93",
|
||||||
|
"sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977",
|
||||||
|
"sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84",
|
||||||
|
"sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b",
|
||||||
|
"sha256:0c489f6c47bbed44918c9c8036a679614920da2a45f481d0eca2ad168ca5327f",
|
||||||
|
"sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33",
|
||||||
|
"sha256:12b38b0f3ddc1342863a6849f4fcb3f506e1d21179ebd34b7aa55a30cb50899f",
|
||||||
|
"sha256:140c5ce21f1eccb254e550c8431825cb716eb76e896202cffa7a0d2a843506da",
|
||||||
|
"sha256:14dea4f0d62ddd1a7f9d7b0003b35a537ac41a2b6205deec8c9c25a8e01748b4",
|
||||||
|
"sha256:15c6bde88f242747258cfee803f3161b7a2c1ffead0817e409d95444a79b4029",
|
||||||
|
"sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b",
|
||||||
|
"sha256:17581a2080012afe603c43005c9d050570e54683dde0d395e3edb4fa9c25f328",
|
||||||
|
"sha256:1a4f960e2e5c1084cf6b1d15482a5556ecc122855d631a2395063ab703d62fdd",
|
||||||
|
"sha256:1d9e0ddfb33a7a78fe92d49aaa2992a78ed5aff4cef7a21d8b1057cca075cc85",
|
||||||
|
"sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d",
|
||||||
|
"sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111",
|
||||||
|
"sha256:2b0e379a15c6b8eb69bb8170d10cfbb8a0dc9126b5402ee8860a2646f4111c3d",
|
||||||
|
"sha256:2d3d1e61191e408a11658a64e9f9bb61741ad28c160576c95dac9df6f74713b4",
|
||||||
|
"sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673",
|
||||||
|
"sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06",
|
||||||
|
"sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36",
|
||||||
|
"sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f",
|
||||||
|
"sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd",
|
||||||
|
"sha256:3b534c62b1887b4bfa80f633485f2a9338f5d46d720b6cc695d2ba8b38d98987",
|
||||||
|
"sha256:3e9276bff9a57100b53e5f9c44469baca1e58ec612e5143db568d69ec27b65ea",
|
||||||
|
"sha256:3f79d241e4833a2a570b6e6639d2114d497011e48a351798bba81fda51560ab7",
|
||||||
|
"sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e",
|
||||||
|
"sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62",
|
||||||
|
"sha256:47f11bf152d8707217feb46e9662a8b1aa3554a8ee56b64d2aa99c3e9914f101",
|
||||||
|
"sha256:48a724dbfad6f4933e2c8a22239980e1b5bc16868df3450cc4ebeb9522b7902f",
|
||||||
|
"sha256:4c33387be8eadc07d0834e0b9e2ead53117fe76ab2dadd37ee80d1df80be4c05",
|
||||||
|
"sha256:4e08e733600647242a9046b6aff888e72fe8a846b00855e5136e7641b08d25d8",
|
||||||
|
"sha256:4f9317da3aa64d0ee93950d3f319b3fe0169500e25c18223715cba39e89808bd",
|
||||||
|
"sha256:50b3dab872001b87052532bd4da3879fda856a2cf6c9418c19bfc94dc290e259",
|
||||||
|
"sha256:524b5311d21741f0b3f48efcdd3442546be3b38507a4e3b0f5138e4340f5dee0",
|
||||||
|
"sha256:53979581e6acdd75b7ce94e6d3b70994f9f8cf1021316d388a159f7f388bdc7f",
|
||||||
|
"sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb",
|
||||||
|
"sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300",
|
||||||
|
"sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b",
|
||||||
|
"sha256:5821c9831fad20cd1a8621a9ed483322ca97a9da9832690a4050ffedcb3e766b",
|
||||||
|
"sha256:58a07f2947aa06ca03d922a86ac30e403ce8282cd15904606bac852bf29ea2ad",
|
||||||
|
"sha256:590acae9e4b0baa895850c0edab988c329a196bacc7326f3249fa5fe7b94e5a8",
|
||||||
|
"sha256:61cf71f62033987ed49b78a19465f40fcbf6f7e94674eda21096ebde6935c2e0",
|
||||||
|
"sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb",
|
||||||
|
"sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8",
|
||||||
|
"sha256:64e8de086d2e4dac41fa286412321469b4535677184e78cc78e5061b44f0e4bf",
|
||||||
|
"sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195",
|
||||||
|
"sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2",
|
||||||
|
"sha256:713cb46d266514db773de52af677aa931cc896a4f5e52f494449c4ff53ce6051",
|
||||||
|
"sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce",
|
||||||
|
"sha256:77fa9826cbc7273e4bc3b7aa289b86936c942fe2c91bc35617c2417e14421592",
|
||||||
|
"sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6",
|
||||||
|
"sha256:7bcb4f360dc9e29b4f5f9fb36b35b429e731373843ccf39a22105bd809ef3138",
|
||||||
|
"sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2",
|
||||||
|
"sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33",
|
||||||
|
"sha256:86cc61c5479ed3b324011aa69484cae8f491b7f58dc0e54acf0894bdb4fae879",
|
||||||
|
"sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b",
|
||||||
|
"sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667",
|
||||||
|
"sha256:8ee3ab33c02a0bd7d219a184c9bc43811de373551529981035673ca2a1ba7b93",
|
||||||
|
"sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1",
|
||||||
|
"sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a",
|
||||||
|
"sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e",
|
||||||
|
"sha256:98a1486861fa3c603a5a3ccd56fc45b9756372bb30f6fb559b898fc2fad82e0d",
|
||||||
|
"sha256:9a6bdc19830703eee91e7eb2d671b165febefbf5eec6a4f163d1833d23be17af",
|
||||||
|
"sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745",
|
||||||
|
"sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc",
|
||||||
|
"sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324",
|
||||||
|
"sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0",
|
||||||
|
"sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8",
|
||||||
|
"sha256:be3ddd26a22d032914cfca5ef7db74f31adbd6c9d88a6f4e21ebd8e057d9474c",
|
||||||
|
"sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02",
|
||||||
|
"sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574",
|
||||||
|
"sha256:c8458becc562ee35b30b5e53173933414cf42e56b3f4f3d80997bf0dda7308d1",
|
||||||
|
"sha256:ca195cd9d1d84b3498532968237774a6e06e2a4afe706b87172f1d033b95e230",
|
||||||
|
"sha256:ccc68ee27362f8d3516deecffa124d1488ae20347628e357264e7e66dbdaba08",
|
||||||
|
"sha256:d3d59479b98cc364b8a08ddd854c7817b5c578a521b56af5a96b3a9db18cc9b7",
|
||||||
|
"sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd",
|
||||||
|
"sha256:dc2af0135139bbb26b1ea5bdc430e049edb745ae643cb898afb32549ce4801de",
|
||||||
|
"sha256:dc7ce867d277aa74555c67b93ef2a6f78bd7bd73e6c2bbafeb96f8bccd05b9d9",
|
||||||
|
"sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1",
|
||||||
|
"sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5",
|
||||||
|
"sha256:e5a64ac6016839fd906b3d7cc1f7ecb145c7d44a310234b6843f3b23b8ec0651",
|
||||||
|
"sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d",
|
||||||
|
"sha256:e74dc488a27b90f31ab307b4cf3f07a45bb78a0e91cfb36d69c6eced4f36089b",
|
||||||
|
"sha256:e82b8e0b88d493d4e882f18de30f679bf1197c82d8c799acb5fdb4068cadb945",
|
||||||
|
"sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883",
|
||||||
|
"sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575",
|
||||||
|
"sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529",
|
||||||
|
"sha256:f38fabd7b8d14fb7d63fbb2d07971d6edd518d2a43542c63c29164c901d2a758",
|
||||||
|
"sha256:f412923d4ce1ec29aa3cf7752598e5eb154f549cfbf62d7c6f3cc76cb25b32e0",
|
||||||
|
"sha256:f4e07df8476545da7cf23f75811f4fc334b06fc50d8e945e897cfc00c8f89690",
|
||||||
|
"sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa",
|
||||||
|
"sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3",
|
||||||
|
"sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211",
|
||||||
|
"sha256:f73e4fcf7455d3b734e6ecbafdbc12d3c1dd8f2146fd186e003ae1c8f00e5eed",
|
||||||
|
"sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1",
|
||||||
|
"sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736",
|
||||||
|
"sha256:fea6d6939d9bf098d96c6d22bb3e4ff39f8eb3f0f26b52c8c69ba06845490095",
|
||||||
|
"sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==2.1.0"
|
||||||
|
},
|
||||||
|
"openpyxl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2",
|
||||||
|
"sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==3.1.5"
|
||||||
|
},
|
||||||
|
"paho-mqtt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834",
|
||||||
|
"sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==2.1.0"
|
||||||
|
},
|
||||||
|
"pandas": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863",
|
||||||
|
"sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2",
|
||||||
|
"sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1",
|
||||||
|
"sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad",
|
||||||
|
"sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db",
|
||||||
|
"sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76",
|
||||||
|
"sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51",
|
||||||
|
"sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32",
|
||||||
|
"sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08",
|
||||||
|
"sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b",
|
||||||
|
"sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4",
|
||||||
|
"sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921",
|
||||||
|
"sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288",
|
||||||
|
"sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee",
|
||||||
|
"sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0",
|
||||||
|
"sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24",
|
||||||
|
"sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99",
|
||||||
|
"sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151",
|
||||||
|
"sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd",
|
||||||
|
"sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce",
|
||||||
|
"sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57",
|
||||||
|
"sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef",
|
||||||
|
"sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54",
|
||||||
|
"sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a",
|
||||||
|
"sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238",
|
||||||
|
"sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23",
|
||||||
|
"sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772",
|
||||||
|
"sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce",
|
||||||
|
"sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.9'",
|
||||||
|
"version": "==2.2.2"
|
||||||
|
},
|
||||||
|
"pillow": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885",
|
||||||
|
"sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea",
|
||||||
|
"sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df",
|
||||||
|
"sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5",
|
||||||
|
"sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c",
|
||||||
|
"sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d",
|
||||||
|
"sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd",
|
||||||
|
"sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06",
|
||||||
|
"sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908",
|
||||||
|
"sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a",
|
||||||
|
"sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be",
|
||||||
|
"sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0",
|
||||||
|
"sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b",
|
||||||
|
"sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80",
|
||||||
|
"sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a",
|
||||||
|
"sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e",
|
||||||
|
"sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9",
|
||||||
|
"sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696",
|
||||||
|
"sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b",
|
||||||
|
"sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309",
|
||||||
|
"sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e",
|
||||||
|
"sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab",
|
||||||
|
"sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d",
|
||||||
|
"sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060",
|
||||||
|
"sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d",
|
||||||
|
"sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d",
|
||||||
|
"sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4",
|
||||||
|
"sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3",
|
||||||
|
"sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6",
|
||||||
|
"sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb",
|
||||||
|
"sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94",
|
||||||
|
"sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b",
|
||||||
|
"sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496",
|
||||||
|
"sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0",
|
||||||
|
"sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319",
|
||||||
|
"sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b",
|
||||||
|
"sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856",
|
||||||
|
"sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef",
|
||||||
|
"sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680",
|
||||||
|
"sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b",
|
||||||
|
"sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42",
|
||||||
|
"sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e",
|
||||||
|
"sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597",
|
||||||
|
"sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a",
|
||||||
|
"sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8",
|
||||||
|
"sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3",
|
||||||
|
"sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736",
|
||||||
|
"sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da",
|
||||||
|
"sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126",
|
||||||
|
"sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd",
|
||||||
|
"sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5",
|
||||||
|
"sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b",
|
||||||
|
"sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026",
|
||||||
|
"sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b",
|
||||||
|
"sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc",
|
||||||
|
"sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46",
|
||||||
|
"sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2",
|
||||||
|
"sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c",
|
||||||
|
"sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe",
|
||||||
|
"sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984",
|
||||||
|
"sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a",
|
||||||
|
"sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70",
|
||||||
|
"sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca",
|
||||||
|
"sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b",
|
||||||
|
"sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91",
|
||||||
|
"sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3",
|
||||||
|
"sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84",
|
||||||
|
"sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1",
|
||||||
|
"sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5",
|
||||||
|
"sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be",
|
||||||
|
"sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f",
|
||||||
|
"sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc",
|
||||||
|
"sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9",
|
||||||
|
"sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e",
|
||||||
|
"sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141",
|
||||||
|
"sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef",
|
||||||
|
"sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22",
|
||||||
|
"sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27",
|
||||||
|
"sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e",
|
||||||
|
"sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==10.4.0"
|
||||||
|
},
|
||||||
|
"psutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35",
|
||||||
|
"sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0",
|
||||||
|
"sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c",
|
||||||
|
"sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1",
|
||||||
|
"sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3",
|
||||||
|
"sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c",
|
||||||
|
"sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd",
|
||||||
|
"sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3",
|
||||||
|
"sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0",
|
||||||
|
"sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2",
|
||||||
|
"sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6",
|
||||||
|
"sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d",
|
||||||
|
"sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c",
|
||||||
|
"sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0",
|
||||||
|
"sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132",
|
||||||
|
"sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14",
|
||||||
|
"sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||||
|
"version": "==6.0.0"
|
||||||
|
},
|
||||||
|
"pydantic": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a",
|
||||||
|
"sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==2.8.2"
|
||||||
|
},
|
||||||
|
"pydantic-core": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d",
|
||||||
|
"sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f",
|
||||||
|
"sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686",
|
||||||
|
"sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482",
|
||||||
|
"sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006",
|
||||||
|
"sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83",
|
||||||
|
"sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6",
|
||||||
|
"sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88",
|
||||||
|
"sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86",
|
||||||
|
"sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a",
|
||||||
|
"sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6",
|
||||||
|
"sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a",
|
||||||
|
"sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6",
|
||||||
|
"sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6",
|
||||||
|
"sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43",
|
||||||
|
"sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c",
|
||||||
|
"sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4",
|
||||||
|
"sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e",
|
||||||
|
"sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203",
|
||||||
|
"sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd",
|
||||||
|
"sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1",
|
||||||
|
"sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24",
|
||||||
|
"sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc",
|
||||||
|
"sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc",
|
||||||
|
"sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3",
|
||||||
|
"sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598",
|
||||||
|
"sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98",
|
||||||
|
"sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331",
|
||||||
|
"sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2",
|
||||||
|
"sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a",
|
||||||
|
"sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6",
|
||||||
|
"sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688",
|
||||||
|
"sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91",
|
||||||
|
"sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa",
|
||||||
|
"sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b",
|
||||||
|
"sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0",
|
||||||
|
"sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840",
|
||||||
|
"sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c",
|
||||||
|
"sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd",
|
||||||
|
"sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3",
|
||||||
|
"sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231",
|
||||||
|
"sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1",
|
||||||
|
"sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953",
|
||||||
|
"sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250",
|
||||||
|
"sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a",
|
||||||
|
"sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2",
|
||||||
|
"sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20",
|
||||||
|
"sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434",
|
||||||
|
"sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab",
|
||||||
|
"sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703",
|
||||||
|
"sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a",
|
||||||
|
"sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2",
|
||||||
|
"sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac",
|
||||||
|
"sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611",
|
||||||
|
"sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121",
|
||||||
|
"sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e",
|
||||||
|
"sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b",
|
||||||
|
"sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09",
|
||||||
|
"sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906",
|
||||||
|
"sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9",
|
||||||
|
"sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7",
|
||||||
|
"sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b",
|
||||||
|
"sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987",
|
||||||
|
"sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c",
|
||||||
|
"sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b",
|
||||||
|
"sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e",
|
||||||
|
"sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237",
|
||||||
|
"sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1",
|
||||||
|
"sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19",
|
||||||
|
"sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b",
|
||||||
|
"sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad",
|
||||||
|
"sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0",
|
||||||
|
"sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94",
|
||||||
|
"sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312",
|
||||||
|
"sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f",
|
||||||
|
"sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669",
|
||||||
|
"sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1",
|
||||||
|
"sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe",
|
||||||
|
"sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99",
|
||||||
|
"sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a",
|
||||||
|
"sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a",
|
||||||
|
"sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52",
|
||||||
|
"sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c",
|
||||||
|
"sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad",
|
||||||
|
"sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1",
|
||||||
|
"sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a",
|
||||||
|
"sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f",
|
||||||
|
"sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a",
|
||||||
|
"sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==2.20.1"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==2.9.0.post0"
|
||||||
|
},
|
||||||
|
"python-multipart": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026",
|
||||||
|
"sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.0.9"
|
||||||
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812",
|
||||||
|
"sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"
|
||||||
|
],
|
||||||
|
"version": "==2024.1"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
|
||||||
|
"sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==2.32.3"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||||
|
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.16.0"
|
||||||
|
},
|
||||||
|
"sniffio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||||
|
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==1.3.1"
|
||||||
|
},
|
||||||
|
"starlette": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:526f53a77f0e43b85f583438aee1a940fd84f8fd610353e8b0c1a77ad8a87e76",
|
||||||
|
"sha256:53a7439060304a208fea17ed407e998f46da5e5d9b1addfea3040094512a6379"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.38.4"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
|
||||||
|
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==4.12.2"
|
||||||
|
},
|
||||||
|
"tzdata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd",
|
||||||
|
"sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2'",
|
||||||
|
"version": "==2024.1"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472",
|
||||||
|
"sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==2.2.2"
|
||||||
|
},
|
||||||
|
"uvicorn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788",
|
||||||
|
"sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==0.30.6"
|
||||||
|
},
|
||||||
|
"xlrd": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd",
|
||||||
|
"sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"
|
||||||
|
],
|
||||||
|
"index": "pip_conf_index_global",
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||||
|
"version": "==2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
99
SCLP/app.py
Normal file
99
SCLP/app.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : api后端服务程序启动入口
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.exceptions import RequestValidationError
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from config import VERSION, PREFIX_PATH
|
||||||
|
from utils import logger, database, misc
|
||||||
|
from models import sessions, houses, devices, products, householders, subsystem, parkinglots
|
||||||
|
from routers import (login, houses_manage, devices_manage, space_manage, householder_manage,
|
||||||
|
access_devices_0, access_devices_1,
|
||||||
|
parkinglot_0, parkinglot_1)
|
||||||
|
|
||||||
|
|
||||||
|
class MainServer:
|
||||||
|
def __init__(self, app_args):
|
||||||
|
self.port = app_args.port
|
||||||
|
self.app = None
|
||||||
|
self.prepare()
|
||||||
|
self.server_main()
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
"""准备处理,一些适配和配置加载工作"""
|
||||||
|
# 数据库初始化
|
||||||
|
logger.Logger.init("数据库执行初始化校验 ...")
|
||||||
|
th = database.get_table_handler()
|
||||||
|
sessions.SessionsTable.check(th)
|
||||||
|
houses.HousesTable.check(th)
|
||||||
|
products.ProductsTable.check(th)
|
||||||
|
devices.DevicesTable.check(th)
|
||||||
|
subsystem.SubsystemTable.check(th)
|
||||||
|
householders.HouseholdersTable.check(th)
|
||||||
|
parkinglots.ParkinglotsTable.check(th)
|
||||||
|
logger.Logger.init("数据库完成初始化校验 ✅")
|
||||||
|
|
||||||
|
self.app = FastAPI(
|
||||||
|
title="Smart Community Local Platform",
|
||||||
|
description="智慧社区本地化平台后端服务",
|
||||||
|
version=VERSION,
|
||||||
|
docs_url=f"{PREFIX_PATH}/docs" # 设为None则会关闭
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册路由模块中的路由
|
||||||
|
self.app.include_router(login.router, prefix=PREFIX_PATH, tags=["登录界面"])
|
||||||
|
self.app.include_router(houses_manage.router, prefix=PREFIX_PATH, tags=["内置信息"])
|
||||||
|
self.app.include_router(householder_manage.router, prefix=PREFIX_PATH, tags=["住户管理界面"])
|
||||||
|
self.app.include_router(access_devices_0.router, prefix=PREFIX_PATH, tags=["通行设备界面"])
|
||||||
|
self.app.include_router(access_devices_1.router, prefix=PREFIX_PATH, tags=["通行授权界面"])
|
||||||
|
self.app.include_router(parkinglot_0.router, prefix=PREFIX_PATH, tags=["车场管理界面"])
|
||||||
|
self.app.include_router(parkinglot_1.router, prefix=PREFIX_PATH, tags=["车场授权界面"])
|
||||||
|
self.app.include_router(devices_manage.router, prefix=PREFIX_PATH, tags=["设备管理界面"])
|
||||||
|
self.app.include_router(space_manage.router, prefix=PREFIX_PATH, tags=["通行空间映射界面"])
|
||||||
|
|
||||||
|
@self.app.exception_handler(RequestValidationError)
|
||||||
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||||
|
logger.Logger.error(f"{request.url.path} 请求参数错误 {exc.errors()} - {request.client.host}")
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=422,
|
||||||
|
content={"status": False,
|
||||||
|
"message": f"请求参数错误,请检查输入"
|
||||||
|
f"{',错误类型:' + exc.errors()[0]['type'] if len(exc.errors()) > 0 else ''}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.app.exception_handler(misc.InvalidException)
|
||||||
|
async def invalid_target_exception_handler(request: Request, exc: misc.InvalidException):
|
||||||
|
logger.Logger.error(f"{request.url.path} {exc.message} - {request.client.host}")
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"status": False, "message": f"{exc.message}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def server_main(self):
|
||||||
|
"""主服务程序"""
|
||||||
|
logger.Logger.debug(f"当前已开启 DEBUG 模式")
|
||||||
|
uvicorn.run(self.app, host="0.0.0.0", port=self.port, access_log=False)
|
||||||
|
|
||||||
|
|
||||||
|
def main(port: str = 9680):
|
||||||
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--port",
|
||||||
|
type=int,
|
||||||
|
default=port,
|
||||||
|
help=f"Port of server, default {logger.new_dc(port)}",
|
||||||
|
)
|
||||||
|
app_args = parser.parse_args()
|
||||||
|
MainServer(app_args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
51
SCLP/auto_callback.py
Normal file
51
SCLP/auto_callback.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 用于自动回复mqtt,用于测试模拟设备下发请求成功
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
from utils import logger
|
||||||
|
from config import BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD
|
||||||
|
|
||||||
|
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
logger.Logger.debug(f"\033[1;32mAUTO CALLBACK\033[0m 🔗 Mqtt connection! {{rc: {rc}}} 🔗")
|
||||||
|
client.subscribe("/jmlink/+/tml/service/call")
|
||||||
|
|
||||||
|
|
||||||
|
def on_disconnect(client, userdata, rc):
|
||||||
|
logger.Logger.debug(f"\033[1;31mAUTO CALLBACK\033[0m 🔌 Break mqtt connection! {{rc: {rc}}} 🔌")
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(client, userdata, message):
|
||||||
|
logger.Logger.debug("\033[1;36mAUTO CALLBACK\033[0m <- 💡 接收到新数据,执行 on_message 中 ...")
|
||||||
|
msg = json.loads(message.payload.decode().replace("\x00", ""))
|
||||||
|
print(f"msg: {msg}")
|
||||||
|
if "messageId" in msg.keys():
|
||||||
|
topic = f"{message.topic}_resp"
|
||||||
|
print(topic)
|
||||||
|
auto_callback = {
|
||||||
|
"messageId": msg["messageId"],
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"code": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.publish(topic, json.dumps(auto_callback))
|
||||||
|
print(auto_callback)
|
||||||
|
|
||||||
|
|
||||||
|
client = mqtt.Client(client_id="auto_callback@python")
|
||||||
|
client.on_connect = on_connect
|
||||||
|
client.on_disconnect = on_disconnect
|
||||||
|
client.on_message = on_message
|
||||||
|
client.username_pw_set(BROKER_USERNAME, BROKER_PASSWD)
|
||||||
|
client.connect(BROKER_HOST, BROKER_PORT)
|
||||||
|
client.loop_start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(86400)
|
||||||
38
SCLP/backend.py
Normal file
38
SCLP/backend.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : mqtt后端服务程序启动入口
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
from config import BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD
|
||||||
|
from utils.misc import create_mqtt_client, UserData, on_connect, on_disconnect, on_publish
|
||||||
|
from device.services import on_message
|
||||||
|
|
||||||
|
|
||||||
|
def main_mqtt():
|
||||||
|
client_dict = {}
|
||||||
|
userdata = UserData()
|
||||||
|
userdata.set_topics([
|
||||||
|
"/jmlink/+/comm/register",
|
||||||
|
"/jmlink/+/comm/sub/register",
|
||||||
|
"/jmlink/+/comm/online",
|
||||||
|
"/jmlink/+/comm/offline",
|
||||||
|
"/jmlink/+/comm/post",
|
||||||
|
"/jmlink/+/tml/event/post",
|
||||||
|
"/jmlink/+/tml/property/post"
|
||||||
|
])
|
||||||
|
userdata.set_clients(client_dict)
|
||||||
|
client = create_mqtt_client(BROKER_HOST, BROKER_PORT,
|
||||||
|
userdata, on_message, on_publish, on_connect, on_disconnect,
|
||||||
|
"backend@python", username=BROKER_USERNAME, password=BROKER_PASSWD)
|
||||||
|
client.loop_start()
|
||||||
|
client_dict["center"] = [client, userdata]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(86400)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main_mqtt()
|
||||||
50
SCLP/config.py
Normal file
50
SCLP/config.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 根据实际情况动态变化的配置
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from utils import logger
|
||||||
|
from utils.misc import get_ip_address
|
||||||
|
|
||||||
|
VERSION = "0.0.0.1"
|
||||||
|
PREFIX_PATH = "/api/v1"
|
||||||
|
DB_PATH = "./data/SCLP.db"
|
||||||
|
FACES_DIR_PATH = "data/FaceImages"
|
||||||
|
SUB_PATH = "faces"
|
||||||
|
|
||||||
|
ENV_FLAG = os.getenv("ENV_FLAG", "DEPLOY")
|
||||||
|
if_name = "WLAN" if os.name != "posix" else "enp1s0" if ENV_FLAG != "WJ14P" else "wlp2s0"
|
||||||
|
LAN_IP, _ = get_ip_address(if_name)
|
||||||
|
if ENV_FLAG == "DEPLOY":
|
||||||
|
if "10.59.246" in LAN_IP:
|
||||||
|
ENV_FLAG = "LAB"
|
||||||
|
if "192.168.1" in LAN_IP:
|
||||||
|
ENV_FLAG = "X13"
|
||||||
|
SLEEP_TIME = 0.03 # mqtt publish后的循环等待返回时间,30ms检测一次
|
||||||
|
TIMEOUT_SECOND = 30 # 等待超时时间,3s
|
||||||
|
|
||||||
|
LOGGER_DEBUG = os.environ.get("LOGGER_DEBUG", "True")
|
||||||
|
logger.DEBUG = False if LOGGER_DEBUG.lower() == "false" else True
|
||||||
|
logger.LOGGER_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "run.log")
|
||||||
|
|
||||||
|
LOCAL_IP = "127.0.0.1"
|
||||||
|
ENV_CONFIG = {
|
||||||
|
"B760M": {"HOST": "b760m.langzihan.top", "PORT": 9680, "BROKER_HOST": LOCAL_IP, "BROKER_PORT": 1883},
|
||||||
|
"M5": {"HOST": "sclp.langzihan.top", "PORT": 80, "BROKER_HOST": LOCAL_IP, "BROKER_PORT": 1883},
|
||||||
|
"X13": {"HOST": LOCAL_IP, "PORT": 9680, "BROKER_HOST": "intranet.b760m.langzihan.top", "BROKER_PORT": 1883},
|
||||||
|
"WJ14P": {"HOST": LOCAL_IP, "PORT": 9680, "BROKER_HOST": "intranet.b760m.langzihan.top", "BROKER_PORT": 1883},
|
||||||
|
"LAB": {"HOST": "10.59.246.170", "PORT": 9680, "BROKER_HOST": "10.59.246.161", "BROKER_PORT": 31883},
|
||||||
|
"DEPLOY": {"HOST": "10.59.246.170", "PORT": 9680, "BROKER_HOST": "10.59.246.161", "BROKER_PORT": 31883},
|
||||||
|
}
|
||||||
|
APP_HOST = ENV_CONFIG[ENV_FLAG]["HOST"]
|
||||||
|
APP_PORT = ENV_CONFIG[ENV_FLAG]["PORT"]
|
||||||
|
IMAGE_SERVER_PORT = APP_PORT + 1
|
||||||
|
|
||||||
|
BROKER_HOST = ENV_CONFIG[ENV_FLAG]["BROKER_HOST"]
|
||||||
|
BROKER_PORT = ENV_CONFIG[ENV_FLAG]["BROKER_PORT"]
|
||||||
|
BROKER_USERNAME = "test@sclp"
|
||||||
|
BROKER_PASSWD = "test@sclp"
|
||||||
|
|
||||||
|
AUTO_CALLBACK = False if ENV_FLAG == "LAB" else True
|
||||||
BIN
SCLP/data/mvboli.ttf
Normal file
BIN
SCLP/data/mvboli.ttf
Normal file
Binary file not shown.
323
SCLP/device/call.py
Normal file
323
SCLP/device/call.py
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 由服务主动调用的一些方法,实现调用后自动连接mqtt进行任务下发和回馈处理,解耦服务端与mqtt
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, field_validator
|
||||||
|
|
||||||
|
from config import (BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWD, PREFIX_PATH, SUB_PATH,
|
||||||
|
SLEEP_TIME, TIMEOUT_SECOND)
|
||||||
|
from config import APP_HOST as HOST_IP
|
||||||
|
from config import IMAGE_SERVER_PORT as PORT
|
||||||
|
from utils import logger
|
||||||
|
from utils.misc import generate_captcha_text, UserData, create_mqtt_client, on_publish, on_disconnect, now_tz_datetime
|
||||||
|
|
||||||
|
|
||||||
|
class CallMessage:
|
||||||
|
def __init__(self, service_code, params):
|
||||||
|
self.messageId = generate_captcha_text(16)
|
||||||
|
self.version = "1.0"
|
||||||
|
self.time = int(time.time() * 1000)
|
||||||
|
self.fromUid = "system"
|
||||||
|
self.serviceCode = service_code
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
_dict = self.__dict__.copy()
|
||||||
|
for key, value in self.__dict__.items():
|
||||||
|
if value is None:
|
||||||
|
_dict.pop(key)
|
||||||
|
_dict["from"] = "backend"
|
||||||
|
return _dict
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return json.dumps(self.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class AddFaceItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
user_id: str | int
|
||||||
|
device_ids: str
|
||||||
|
start_date: str = now_tz_datetime()
|
||||||
|
phone_number: str = ""
|
||||||
|
face_url: str
|
||||||
|
expire_date: Optional[str] = "2099-01-01T01:01:01.001Z"
|
||||||
|
|
||||||
|
@field_validator("user_id")
|
||||||
|
def check_user_id(cls, value):
|
||||||
|
if isinstance(value, int):
|
||||||
|
return str(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class DelFaceItem(BaseModel):
|
||||||
|
user_id: str | int
|
||||||
|
device_ids: str
|
||||||
|
|
||||||
|
@field_validator("user_id")
|
||||||
|
def check_user_id(cls, value):
|
||||||
|
if isinstance(value, int):
|
||||||
|
return str(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
ACTION = {
|
||||||
|
"insert": "新增",
|
||||||
|
"update": "更新",
|
||||||
|
"delete": "删除"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VehicleRegistrationItem(BaseModel):
|
||||||
|
car_plate_no: str # 车牌
|
||||||
|
registration_id: str
|
||||||
|
owner_id: str
|
||||||
|
owner_name: str
|
||||||
|
registration_time: str
|
||||||
|
begin_time: str
|
||||||
|
end_time: str = "2099-01-01T01:01:01.001Z"
|
||||||
|
action: str # insert, update, delete
|
||||||
|
registration_type: str # 0: 业主,1: 访客
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateFixedCardItem(BaseModel):
|
||||||
|
vehicle_owner_id: str
|
||||||
|
vehicle_owner_name: str
|
||||||
|
vehicle_owner_phone: str
|
||||||
|
plate_number: str # 车牌
|
||||||
|
effective_date_begin: str
|
||||||
|
effective_date_end: str
|
||||||
|
action: str # insert, update, delete
|
||||||
|
card_id: str # 在新增时进行生成
|
||||||
|
|
||||||
|
|
||||||
|
def on_connect(client, userdata, _flags, rc):
|
||||||
|
logger.Logger.debug(f"🔗 Mqtt connection! {{rc: {rc}}} 🔗")
|
||||||
|
if userdata.topics:
|
||||||
|
_topics = [(topic, 0) for topic in userdata.topics]
|
||||||
|
client.subscribe(_topics)
|
||||||
|
logger.Logger.debug(f"subscribe topics: {userdata.topics}")
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(_client, userdata: UserData, message):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug("💡 接收到新数据,执行 on_message 中 ...", log_path=None)
|
||||||
|
msg = json.loads(message.payload.decode().replace("\x00", ""))
|
||||||
|
logger.Logger.info(f"{message.topic}: {msg}")
|
||||||
|
userdata.set_topic(message.topic)
|
||||||
|
if userdata.token:
|
||||||
|
if "messageId" in msg.keys() and userdata.token == msg["messageId"]: # 兼容aiot平台
|
||||||
|
userdata.set_status_add("status", True)
|
||||||
|
userdata.set_status_add("response", msg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"{type(e).__name__}, {e}")
|
||||||
|
if logger.DEBUG:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesCall:
|
||||||
|
def __init__(self):
|
||||||
|
# 创建mqtt连接
|
||||||
|
self.userdata = UserData()
|
||||||
|
self.client = create_mqtt_client(BROKER_HOST, BROKER_PORT,
|
||||||
|
self.userdata, on_message, on_publish, on_connect, on_disconnect,
|
||||||
|
username=BROKER_USERNAME, password=BROKER_PASSWD)
|
||||||
|
self.client.loop_start()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.client.loop_stop()
|
||||||
|
|
||||||
|
def add_face(self, device_id, obj: AddFaceItem) -> tuple[bool, int, str]:
|
||||||
|
if obj.face_url.startswith("http"):
|
||||||
|
face_filename = obj.face_url.split('/')[-1]
|
||||||
|
else:
|
||||||
|
face_filename = obj.face_url
|
||||||
|
obj.face_url = f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{face_filename}"
|
||||||
|
topic = f"/jmlink/{device_id}/tml/service/call"
|
||||||
|
self.userdata.set_topic(topic)
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🚩 AddFace 🚩 {'=' * 10}")
|
||||||
|
logger.Logger.debug(f"{obj.device_ids} 添加用户 {obj.user_id} 的人脸授权")
|
||||||
|
cm = CallMessage("add_face", obj.__dict__)
|
||||||
|
self.userdata.set_message(cm.dict())
|
||||||
|
# 等待回传
|
||||||
|
self.userdata.set_token(cm.messageId)
|
||||||
|
self.userdata.set_status_add("status", False)
|
||||||
|
self.userdata.set_status_add("start_timestamp", time.time())
|
||||||
|
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
|
||||||
|
self.client.subscribe(topic_resp)
|
||||||
|
self.client.publish(topic, cm())
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
if self.userdata.status["status"]:
|
||||||
|
if "code" in self.userdata.status["response"].keys():
|
||||||
|
if self.userdata.status["response"]["code"] != 0:
|
||||||
|
error_code = self.userdata.status['response']['code']
|
||||||
|
logger.Logger.error(
|
||||||
|
f"用户 - {obj.user_id} 在设备 - {device_id} 上人脸下置失败,错误码 {error_code}")
|
||||||
|
return False, error_code, self.userdata.status['response']['message']
|
||||||
|
else:
|
||||||
|
logger.Logger.info(f"设备 - {obj.device_ids} 用户({obj.user_id}) 人脸完成授权")
|
||||||
|
return True, 0, ""
|
||||||
|
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
|
||||||
|
logger.Logger.error("等待回复超时")
|
||||||
|
return False, -1, "等待回复超时"
|
||||||
|
time.sleep(SLEEP_TIME)
|
||||||
|
finally:
|
||||||
|
self.client.unsubscribe(topic_resp)
|
||||||
|
logger.Logger.debug(f"移除订阅: {topic_resp}")
|
||||||
|
self.userdata.set_token(None)
|
||||||
|
self.userdata.set_status_remove("response")
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🏁 AddFace 🏁 {'=' * 10}")
|
||||||
|
|
||||||
|
def del_face(self, device_id, obj: DelFaceItem) -> tuple[bool, int, str]:
|
||||||
|
topic = f"/jmlink/{device_id}/tml/service/call"
|
||||||
|
self.userdata.set_topic(topic)
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🚩 DelFace 🚩 {'=' * 10}")
|
||||||
|
logger.Logger.debug(f"从 {obj.device_ids} 移除用户 {obj.user_id} 的人脸授权")
|
||||||
|
cm = CallMessage("del_face", obj.__dict__)
|
||||||
|
self.userdata.set_message(cm.dict())
|
||||||
|
# 等待回传
|
||||||
|
self.userdata.set_token(cm.messageId)
|
||||||
|
self.userdata.set_status_add("status", False)
|
||||||
|
self.userdata.set_status_add("start_timestamp", time.time())
|
||||||
|
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
|
||||||
|
self.client.subscribe(topic_resp)
|
||||||
|
self.client.publish(topic, cm())
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
if self.userdata.status["status"]:
|
||||||
|
if "code" in self.userdata.status["response"].keys():
|
||||||
|
if self.userdata.status["response"]["code"] != 0:
|
||||||
|
error_code = self.userdata.status['response']['code']
|
||||||
|
logger.Logger.error(
|
||||||
|
f"用户 - {obj.user_id} 在设备 - {device_id} 上人脸移除失败,错误码 {error_code}")
|
||||||
|
return False, error_code, self.userdata.status['response']['message']
|
||||||
|
else:
|
||||||
|
logger.Logger.info(f"设备 - {obj.device_ids} 用户({obj.user_id}) 人脸完成移除")
|
||||||
|
return True, 0, ""
|
||||||
|
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
|
||||||
|
logger.Logger.error("等待回复超时")
|
||||||
|
return False, -1, "等待回复超时"
|
||||||
|
time.sleep(SLEEP_TIME)
|
||||||
|
finally:
|
||||||
|
self.client.unsubscribe(topic_resp)
|
||||||
|
logger.Logger.debug(f"移除订阅: {topic_resp}")
|
||||||
|
self.userdata.set_token(None)
|
||||||
|
self.userdata.set_status_remove("response")
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🏁 DelFace 🏁 {'=' * 10}")
|
||||||
|
|
||||||
|
def vehicle_registration(self, device_id, obj: VehicleRegistrationItem) -> tuple[bool, int, str]:
|
||||||
|
topic = f"/jmlink/{device_id}/tml/service/call"
|
||||||
|
self.userdata.set_topic(topic)
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🚩 VehicleRegistration 🚩 {'=' * 10}")
|
||||||
|
logger.Logger.debug(f"{device_id} {ACTION[obj.action]}车辆 {obj.car_plate_no} 信息")
|
||||||
|
cm = CallMessage("vehicle_registration", obj.__dict__)
|
||||||
|
self.userdata.set_message(cm.dict())
|
||||||
|
# 等待回传
|
||||||
|
self.userdata.set_token(cm.messageId)
|
||||||
|
self.userdata.set_status_add("status", False)
|
||||||
|
self.userdata.set_status_add("start_timestamp", time.time())
|
||||||
|
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
|
||||||
|
self.client.subscribe(topic_resp)
|
||||||
|
self.client.publish(topic, cm())
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
if self.userdata.status["status"]:
|
||||||
|
if "code" in self.userdata.status["response"].keys():
|
||||||
|
if self.userdata.status["response"]["code"] != 0:
|
||||||
|
error_code = self.userdata.status['response']['code']
|
||||||
|
logger.Logger.error(f"{topic_resp} 返回错误码: {error_code}")
|
||||||
|
return False, error_code, self.userdata.status['response']['message']
|
||||||
|
else:
|
||||||
|
logger.Logger.info(f"车辆({obj.car_plate_no}) 信息完成{ACTION[obj.action]}")
|
||||||
|
return True, 0, ""
|
||||||
|
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
|
||||||
|
logger.Logger.error("等待回复超时")
|
||||||
|
return False, -1, "等待回复超时"
|
||||||
|
time.sleep(SLEEP_TIME)
|
||||||
|
finally:
|
||||||
|
self.client.unsubscribe(topic_resp)
|
||||||
|
logger.Logger.debug(f"移除订阅: {topic_resp}")
|
||||||
|
self.userdata.set_token(None)
|
||||||
|
self.userdata.set_status_remove("response")
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🏁 VehicleRegistration 🏁 {'=' * 10}")
|
||||||
|
|
||||||
|
def update_fixed_card(self, device_id, obj: UpdateFixedCardItem) -> tuple[bool, int, str]:
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🚩 UpdateFixedCard 🚩 {'=' * 10}")
|
||||||
|
topic = f"/jmlink/{device_id}/tml/service/call"
|
||||||
|
self.userdata.set_topic(topic)
|
||||||
|
logger.Logger.debug(f"{device_id} {ACTION[obj.action]}用户 {obj.plate_number} 的月卡信息")
|
||||||
|
cm = CallMessage("update_fixed_card", obj.__dict__)
|
||||||
|
self.userdata.set_message(cm.dict())
|
||||||
|
# 等待回传
|
||||||
|
self.userdata.set_token(cm.messageId)
|
||||||
|
self.userdata.set_status_add("status", False)
|
||||||
|
self.userdata.set_status_add("start_timestamp", time.time())
|
||||||
|
topic_resp = f"/jmlink/{device_id}/tml/service/call_resp"
|
||||||
|
self.client.subscribe(topic_resp)
|
||||||
|
self.client.publish(topic, cm())
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
if self.userdata.status["status"]:
|
||||||
|
if "code" in self.userdata.status["response"].keys():
|
||||||
|
if self.userdata.status["response"]["code"] != 0:
|
||||||
|
error_code = self.userdata.status['response']['code']
|
||||||
|
logger.Logger.error(f"{topic_resp} 返回错误码: {error_code}")
|
||||||
|
return False, error_code, self.userdata.status['response']['message']
|
||||||
|
else:
|
||||||
|
logger.Logger.info(f"车辆({obj.plate_number}) 月卡信息完成修改")
|
||||||
|
return True, 0, ""
|
||||||
|
if time.time() - self.userdata.status["start_timestamp"] > TIMEOUT_SECOND: # 超时跳出
|
||||||
|
logger.Logger.error("等待回复超时")
|
||||||
|
return False, -1, "等待回复超时"
|
||||||
|
time.sleep(SLEEP_TIME)
|
||||||
|
finally:
|
||||||
|
self.client.unsubscribe(topic_resp)
|
||||||
|
logger.Logger.debug(f"移除订阅: {topic_resp}")
|
||||||
|
self.userdata.set_token(None)
|
||||||
|
self.userdata.set_status_remove("response")
|
||||||
|
logger.Logger.debug(f"{'=' * 10} 🏁 UpdateFixedCard 🏁 {'=' * 10}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sc = ServicesCall()
|
||||||
|
sc.del_face(2, DelFaceItem(
|
||||||
|
user_id="1234",
|
||||||
|
device_ids=str([2])
|
||||||
|
))
|
||||||
|
sc.add_face(2, AddFaceItem(
|
||||||
|
name="测试",
|
||||||
|
user_id="ceshi",
|
||||||
|
face_url="http://ip:port/api/v1/faces/person_demo.jpg",
|
||||||
|
device_ids=str([2, 321]),
|
||||||
|
start_date="2024-01-01T18:00:00.000Z"
|
||||||
|
))
|
||||||
|
|
||||||
|
sc.vehicle_registration(2, VehicleRegistrationItem(
|
||||||
|
car_plate_no="鲁G95339",
|
||||||
|
registration_id="5a12265cf73f261148770c12328bb195",
|
||||||
|
owner_id="1244668310399733760",
|
||||||
|
owner_name="测试业主",
|
||||||
|
registration_time="2024-05-27T16:12:26.895Z",
|
||||||
|
begin_time="2024-05-27T16:12:26.895Z",
|
||||||
|
end_time="2034-05-27T16:12:26.895Z",
|
||||||
|
action="insert",
|
||||||
|
registration_type='0'
|
||||||
|
))
|
||||||
|
|
||||||
|
sc.update_fixed_card(2, UpdateFixedCardItem(
|
||||||
|
vehicle_owner_id="1244668310399733760",
|
||||||
|
vehicle_owner_name="测试业主",
|
||||||
|
vehicle_owner_phone="1244668310399733760",
|
||||||
|
action="insert",
|
||||||
|
card_id=str(int(time.time() * 1000)),
|
||||||
|
effective_date_begin=now_tz_datetime(),
|
||||||
|
effective_date_end=now_tz_datetime(365 * 10),
|
||||||
|
plate_number="鲁G95339"
|
||||||
|
))
|
||||||
100
SCLP/device/message.py
Normal file
100
SCLP/device/message.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : None
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class BaseResp:
|
||||||
|
def __init__(self, message_id, data):
|
||||||
|
self.message_id = message_id
|
||||||
|
self.time = int(time.time() * 1000)
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
if not isinstance(self.data, list):
|
||||||
|
if isinstance(self.data, dict):
|
||||||
|
data = self.data
|
||||||
|
else:
|
||||||
|
data = self.data()
|
||||||
|
else:
|
||||||
|
data = [i if isinstance(i, dict) else i() for i in self.data]
|
||||||
|
return {
|
||||||
|
"messageId": self.message_id,
|
||||||
|
"time": self.time,
|
||||||
|
"data": data
|
||||||
|
}
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return json.dumps(self.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleResp:
|
||||||
|
def __init__(self, message_id, code):
|
||||||
|
self.message_id = message_id
|
||||||
|
self.time = int(time.time() * 1000)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {
|
||||||
|
"messageId": self.message_id,
|
||||||
|
"time": self.time,
|
||||||
|
"code": self.code
|
||||||
|
}
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return json.dumps(self.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorResp:
|
||||||
|
def __init__(self, message_id, code):
|
||||||
|
self.message_id = message_id
|
||||||
|
self.time = int(time.time() * 1000)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
return {
|
||||||
|
"messageId": self.message_id,
|
||||||
|
"time": self.time,
|
||||||
|
"data": {"code": self.code}
|
||||||
|
}
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return json.dumps(self.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class DRRespItem:
|
||||||
|
"""设备注册返回数据体"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: str, device_name: str, device_secret: str, code: int):
|
||||||
|
self.device_id = device_id
|
||||||
|
self.device_name = device_name
|
||||||
|
self.device_secret = device_secret
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return {
|
||||||
|
"deviceId": self.device_id,
|
||||||
|
"deviceName": self.device_name,
|
||||||
|
"deviceSecret": self.device_secret,
|
||||||
|
"code": self.code
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SDRRespItem:
|
||||||
|
"""设备注册返回数据体"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: str, device_name: str, code: int):
|
||||||
|
self.device_id = device_id
|
||||||
|
self.device_name = device_name
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return {
|
||||||
|
"deviceId": self.device_id,
|
||||||
|
"deviceName": self.device_name,
|
||||||
|
"code": self.code
|
||||||
|
}
|
||||||
520
SCLP/device/services.py
Normal file
520
SCLP/device/services.py
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : None
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
from config import DB_PATH
|
||||||
|
from device.message import ErrorResp, DRRespItem, BaseResp, SDRRespItem, SimpleResp
|
||||||
|
from models.devices import DevicesTable, DeviceRegister
|
||||||
|
from models.products import ProductsTable
|
||||||
|
from utils.misc import generate_captcha_text, now_datetime_second
|
||||||
|
from utils import logger
|
||||||
|
from utils.database import SQLiteDatabaseEngine, BaseTable
|
||||||
|
from utils.misc import UserData
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(_client, userdata: UserData, message):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug("💡 接收到新数据,执行 on_message 中 ...", log_path=None)
|
||||||
|
msg = json.loads(message.payload.decode().replace("\x00", ""))
|
||||||
|
logger.Logger.info(f"{message.topic}: {msg}")
|
||||||
|
userdata.set_topic(message.topic)
|
||||||
|
if userdata.table_handler is None:
|
||||||
|
_db = SQLiteDatabaseEngine(db_path=DB_PATH)
|
||||||
|
userdata.set_table_handler(BaseTable(_db.connection, _db.cursor))
|
||||||
|
if userdata.token:
|
||||||
|
if "messageId" in msg.keys() and userdata.token == msg["messageId"]: # 兼容aiot平台
|
||||||
|
userdata.set_status_add("status", True)
|
||||||
|
userdata.set_status_add("response", msg)
|
||||||
|
else:
|
||||||
|
auto_service(msg, userdata)
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"{type(e).__name__}, {e}")
|
||||||
|
if logger.DEBUG:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseService:
|
||||||
|
def handle(self, **kwargs):
|
||||||
|
"""must be `override`"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Services:
|
||||||
|
"""业务逻辑入口"""
|
||||||
|
|
||||||
|
class DeviceRegisterService(BaseService):
|
||||||
|
"""直连设备、网关设备注册"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("设备注册请求已接收,正在执行注册 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
# 获取产品信息
|
||||||
|
params = msg["params"]
|
||||||
|
product = ProductsTable.get_product_info(userdata.table_handler, params["productId"])
|
||||||
|
if product is None:
|
||||||
|
logger.Logger.error("DeviceRegisterService -> 产品ID不存在")
|
||||||
|
message = ErrorResp(message_id, 601)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if product.node_type not in ["网关设备", "直连设备"]:
|
||||||
|
logger.Logger.error("DeviceRegisterService -> 节点类型错误")
|
||||||
|
message = ErrorResp(message_id, 503)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
sct = generate_captcha_text(32)
|
||||||
|
dr = DeviceRegister(
|
||||||
|
device_mac=params["deviceName"],
|
||||||
|
device_name=params["displayName"] if params["displayName"] else "",
|
||||||
|
device_desc=params["desc"] if params["desc"] else "",
|
||||||
|
third_local_device_id=params["thirdLocalDeviceId"] if params["thirdLocalDeviceId"] else "",
|
||||||
|
device_sct=sct,
|
||||||
|
gateway_id=None,
|
||||||
|
gateway_name=None,
|
||||||
|
product_name=product.product_name,
|
||||||
|
node_type=product.node_type
|
||||||
|
)
|
||||||
|
device_id = DevicesTable.insert_by_device_register(userdata.table_handler, dr)
|
||||||
|
resp = BaseResp(message_id, DRRespItem(
|
||||||
|
str(device_id),
|
||||||
|
params["displayName"],
|
||||||
|
sct,
|
||||||
|
0
|
||||||
|
))
|
||||||
|
userdata.set_message(resp.dict())
|
||||||
|
client.publish(topic, resp())
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("DeviceRegisterService -> 关键参数缺失")
|
||||||
|
message = ErrorResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class SubDeviceRegisterService(BaseService):
|
||||||
|
"""子设备注册"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("子设备注册请求已接收,正在执行注册 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
gateway_id = int(userdata.topic.split("/")[2])
|
||||||
|
gateway_info = DevicesTable.get_device_info(userdata.table_handler, gateway_id)
|
||||||
|
if gateway_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("SubDeviceRegisterService -> 并无对应上级设备")
|
||||||
|
message = ErrorResp(message_id, 601)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if gateway_info.node_type != "网关设备": # 确保当前主题是网关设备
|
||||||
|
logger.Logger.error("SubDeviceRegisterService -> 节点类型错误")
|
||||||
|
message = ErrorResp(message_id, 503)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
data = []
|
||||||
|
for params in msg["params"]:
|
||||||
|
product_info = ProductsTable.get_product_info(userdata.table_handler, params["productId"])
|
||||||
|
if product_info is None:
|
||||||
|
logger.Logger.error("SubDeviceRegisterService -> 产品ID不存在")
|
||||||
|
data.append({"code": 601})
|
||||||
|
continue
|
||||||
|
if product_info.node_type != "网关子设备": # 确保注册的产品是网关子设备
|
||||||
|
logger.Logger.error("SubDeviceRegisterService -> 产品ID节点类型错误")
|
||||||
|
data.append({"code": 503})
|
||||||
|
continue
|
||||||
|
dr = DeviceRegister(
|
||||||
|
device_mac=params["deviceName"],
|
||||||
|
device_name=params["displayName"] if params["displayName"] else "",
|
||||||
|
device_desc=params["desc"] if params["desc"] else "",
|
||||||
|
third_local_device_id=params["thirdLocalDeviceId"] if params["thirdLocalDeviceId"] else "",
|
||||||
|
device_sct=None,
|
||||||
|
gateway_id=gateway_id,
|
||||||
|
gateway_name=gateway_info.device_name,
|
||||||
|
product_name=product_info.product_name,
|
||||||
|
node_type=product_info.node_type
|
||||||
|
)
|
||||||
|
device_id = DevicesTable.insert_by_device_register(userdata.table_handler, dr)
|
||||||
|
data.append(SDRRespItem(
|
||||||
|
str(device_id),
|
||||||
|
params["displayName"] if params["displayName"] else "",
|
||||||
|
0
|
||||||
|
))
|
||||||
|
message = BaseResp(message_id, data)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
except (KeyError, TypeError) as e:
|
||||||
|
logger.Logger.error("DeviceRegisterService -> 关键参数缺失")
|
||||||
|
logger.Logger.error(f"{type(e).__name__}, {e}")
|
||||||
|
if logger.DEBUG:
|
||||||
|
traceback.print_exc()
|
||||||
|
message = ErrorResp(message_id, 401)
|
||||||
|
userdata.set_message(message)
|
||||||
|
client.publish(topic,
|
||||||
|
{"messageId": message_id, "time": int(time.time() * 1000), "data": [{"code": 401}]})
|
||||||
|
|
||||||
|
class UpdateDeviceStatusService(BaseService):
|
||||||
|
"""设备在线和离线状态的变更"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
message_type = userdata.topic.split("/")[-1]
|
||||||
|
if message_type == "online":
|
||||||
|
device_status = "在线"
|
||||||
|
else:
|
||||||
|
device_status = "离线"
|
||||||
|
logger.Logger.debug(f"设备状态更新请求已接收,正在执行{device_status} ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("UpdateDeviceStatusService -> 并无对应设备")
|
||||||
|
message = ErrorResp(message_id, 40014)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if message_type == "offline" and device_info.node_type == "网关设备":
|
||||||
|
DevicesTable.offline_gateway(userdata.table_handler, device_id)
|
||||||
|
else:
|
||||||
|
DevicesTable.update_device_status(userdata.table_handler, device_id, device_status)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("UpdateDeviceStatusService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class PropertyService(BaseService):
|
||||||
|
"""设备属性上报事件"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("设备属性上报事件已接收,正在处理 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.info("PropertyService -> 并无对应设备")
|
||||||
|
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if "停车" not in device_info.product_name: # 只处理停车场设备的信息上报
|
||||||
|
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
data = msg["values"]
|
||||||
|
nt = now_datetime_second()
|
||||||
|
# 判断是否存在对应停车场,若不存在进行新建,若存在则更新除ID之外的信息
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO parkinglots
|
||||||
|
(id, number, name, type, update_datetime)
|
||||||
|
VALUES (?, ?, ?, 'parkinglot', ?)
|
||||||
|
ON CONFLICT (id, type)
|
||||||
|
DO UPDATE SET number = ?, name = ?, update_datetime = ?
|
||||||
|
""", (data["parkinglot_no"], device_id, data["parkinglot_name"], nt,
|
||||||
|
device_id, data["parkinglot_name"], nt)
|
||||||
|
)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("PropertyService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class AreaService(BaseService):
|
||||||
|
"""车场区域信息变化同步事件"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("车场区域信息变化同步事件已接收,正在处理 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("AreaService -> 并无对应设备")
|
||||||
|
message = SimpleResp(message_id, 40014)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
|
||||||
|
logger.Logger.error("AreaService -> 节点类型错误")
|
||||||
|
message = SimpleResp(message_id, 503)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
params = msg["params"]
|
||||||
|
# ["区域ID","父区域ID","区域编码","区域名称","车位总数"]
|
||||||
|
# {"area_name":"地面","area_no":"1","place_numbers":"60","area_id":"1","parent_area_id":"1"}
|
||||||
|
if params["parent_area_id"] == params["area_id"]:
|
||||||
|
params["parent_area_id"] = None
|
||||||
|
nt = now_datetime_second()
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
|
||||||
|
res = userdata.table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
parkinglot_number = res[0][0]
|
||||||
|
# 判断是否存在对应区域,若不存在进行新建,若存在则更新除ID之外的信息
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR REPLACE INTO parkinglots
|
||||||
|
(id, number, name, type, area_id, parkinglot_id, update_datetime)
|
||||||
|
VALUES (?, ?, ?, 'area', ?, ?, ?)
|
||||||
|
""", (params["area_id"], params["area_no"], params["area_name"], params["parent_area_id"],
|
||||||
|
parkinglot_number, nt)
|
||||||
|
)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
else:
|
||||||
|
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("AreaService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class ChannelService(BaseService):
|
||||||
|
"""车场通道信息变化同步事件"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("车场通道信息变化同步事件已接收,正在执行处理 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("ChannelService -> 并无对应设备")
|
||||||
|
message = SimpleResp(message_id, 40014)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
|
||||||
|
logger.Logger.error("ChannelService -> 节点类型错误")
|
||||||
|
message = SimpleResp(message_id, 503)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
params = msg["params"]
|
||||||
|
# ["区域ID","通道ID","通道编码","通道名称","通道类型"]
|
||||||
|
# {"channel_name":"测试出口","channel_no":"13","area_id":"1","channel_type":"1","channel_id":"13"}
|
||||||
|
channel_type = int(params["channel_type"])
|
||||||
|
if channel_type == 0:
|
||||||
|
params["channel_type"] = "入口"
|
||||||
|
elif channel_type == 1:
|
||||||
|
params["channel_type"] = "出口"
|
||||||
|
else:
|
||||||
|
params["channel_type"] = "出入口"
|
||||||
|
nt = now_datetime_second()
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
|
||||||
|
res = userdata.table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
parkinglot_number = res[0][0]
|
||||||
|
# 判断是否存在对应区域,若不存在进行新建,若存在则更新除ID之外的信息
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR REPLACE INTO parkinglots
|
||||||
|
(id, number, name, type, area_id, parkinglot_id, channel_type, update_datetime)
|
||||||
|
VALUES (?, ?, ?, 'channel', ?, ?, ?, ?)
|
||||||
|
""", (params["channel_id"], params["channel_no"], params["channel_name"],
|
||||||
|
params["area_id"], parkinglot_number, params["channel_type"], nt)
|
||||||
|
)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
else:
|
||||||
|
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("ChannelService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class GateService(BaseService):
|
||||||
|
"""道闸信息同步"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("道闸信息同步事件已接收,正在执行处理 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("ChanGateService -> 并无对应设备")
|
||||||
|
message = SimpleResp(message_id, 40014)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
if "停车" not in device_info.product_name: # 确保当前主题是停车场设备
|
||||||
|
logger.Logger.error("GateService -> 节点类型错误")
|
||||||
|
message = SimpleResp(message_id, 503)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
params = msg["params"]
|
||||||
|
# ["区域ID","通道ID","道闸编码","道闸ID","道闸名称","道闸品牌","道闸状态","运行状态","是否在线"]
|
||||||
|
# {"gate_id":"13","gate_name":"测试出口","is_online":"0","gate_no":"13","area_id":"1","channel_id":"13"}
|
||||||
|
if "is_online" in params.keys():
|
||||||
|
is_online = int(params["is_online"])
|
||||||
|
if is_online == 0:
|
||||||
|
params["is_online"] = "下线"
|
||||||
|
else:
|
||||||
|
params["is_online"] = "上线"
|
||||||
|
else:
|
||||||
|
params["is_online"] = ""
|
||||||
|
if "gate_status" in params.keys():
|
||||||
|
gate_status = int(params["gate_status"])
|
||||||
|
if gate_status == 0:
|
||||||
|
params["gate_status"] = "关闭"
|
||||||
|
elif gate_status == 1:
|
||||||
|
params["gate_status"] = "开启"
|
||||||
|
elif gate_status == 2:
|
||||||
|
params["gate_status"] = "常开"
|
||||||
|
else:
|
||||||
|
params["gate_status"] = "常闭"
|
||||||
|
else:
|
||||||
|
params["gate_status"] = ""
|
||||||
|
if "running_status" in params.keys():
|
||||||
|
running_status = int(params["running_status"])
|
||||||
|
if running_status == 0:
|
||||||
|
params["running_status"] = "正常"
|
||||||
|
elif running_status == 1:
|
||||||
|
params["running_status"] = "故障"
|
||||||
|
else:
|
||||||
|
params["running_status"] = ""
|
||||||
|
nt = now_datetime_second()
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"SELECT id FROM parkinglots WHERE number = ? AND type = 'parkinglot'", (device_id,))
|
||||||
|
res = userdata.table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
parkinglot_number = res[0][0]
|
||||||
|
# 判断是否存在对应区域,若不存在进行新建,若存在则更新除ID之外的信息
|
||||||
|
userdata.table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR REPLACE INTO parkinglots
|
||||||
|
(id, number, name, type, area_id, parkinglot_id, channel_id,
|
||||||
|
is_online, gate_status, running_status, update_datetime)
|
||||||
|
VALUES (?, ?, ?, 'gate', ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (params["gate_id"], params["gate_no"], params["gate_name"],
|
||||||
|
params.get("area_id", ""), parkinglot_number, params.get("channel_id", ""),
|
||||||
|
params["is_online"], params["gate_status"], params["running_status"], nt)
|
||||||
|
)
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
else:
|
||||||
|
message = SimpleResp(message_id, 40014) # 对应设备属性没有上报,数据库中未记载设备编码
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("GateService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class NoHandlePostService(BaseService):
|
||||||
|
"""忽略的事件上报,直接返回成功的反馈"""
|
||||||
|
|
||||||
|
def handle(self, msg: dict, userdata: UserData):
|
||||||
|
logger.Logger.debug("忽略的事件上报请求已接收,即将给予成功回馈 ...", log_path=None)
|
||||||
|
client = userdata.clients["center"][0]
|
||||||
|
topic = f"{userdata.topic}_resp"
|
||||||
|
userdata.set_topic(topic)
|
||||||
|
message_id = msg["messageId"] if "messageId" in msg.keys() else "1"
|
||||||
|
try:
|
||||||
|
device_id = int(userdata.topic.split("/")[2])
|
||||||
|
device_info = DevicesTable.get_device_info(userdata.table_handler, device_id)
|
||||||
|
if device_info is None: # 确保当前主题中的设备ID存在
|
||||||
|
logger.Logger.error("SubDeviceRegisterService -> 并无对应设备")
|
||||||
|
message = SimpleResp(message_id, 601)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
return
|
||||||
|
message = SimpleResp(message_id, 0)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
logger.Logger.error("UpdateDeviceStatusService -> 关键参数缺失")
|
||||||
|
message = SimpleResp(message_id, 401)
|
||||||
|
userdata.set_message(message.dict())
|
||||||
|
client.publish(topic, message())
|
||||||
|
|
||||||
|
class UpdateFixedCardService(BaseService):
|
||||||
|
def handle(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class VehicleRegistration(BaseService):
|
||||||
|
def handle(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def auto_service(msg: dict, userdata: UserData):
|
||||||
|
"""自动化加载不同的服务层"""
|
||||||
|
message_type = userdata.topic.split("/")[-1]
|
||||||
|
servicer = None
|
||||||
|
if message_type == "register": # 设备注册
|
||||||
|
if userdata.topic.split("/")[-2] == "sub":
|
||||||
|
servicer = Services.SubDeviceRegisterService()
|
||||||
|
else:
|
||||||
|
servicer = Services.DeviceRegisterService()
|
||||||
|
elif message_type in ["online", "offline"]: # 设备上下线
|
||||||
|
servicer = Services.UpdateDeviceStatusService()
|
||||||
|
elif message_type == "post": # 事件上报,一般是通行事件,不做处理,直接返回成功
|
||||||
|
if userdata.topic.split("/")[-2] == "property":
|
||||||
|
servicer = Services.PropertyService()
|
||||||
|
elif "eventCode" in msg.keys() and msg["eventCode"] in ["area", "channel", "gate"]:
|
||||||
|
if msg["eventCode"] == "area":
|
||||||
|
servicer = Services.AreaService()
|
||||||
|
elif msg["eventCode"] == "channel":
|
||||||
|
servicer = Services.ChannelService()
|
||||||
|
elif msg["eventCode"] == "gate":
|
||||||
|
servicer = Services.GateService()
|
||||||
|
else:
|
||||||
|
servicer = Services.NoHandlePostService()
|
||||||
|
|
||||||
|
# 构建服务层实体并进行调用
|
||||||
|
if servicer is not None:
|
||||||
|
servicer.handle(msg, userdata)
|
||||||
80
SCLP/image_server.py
Normal file
80
SCLP/image_server.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : api后端服务程序启动入口
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
import aiofiles
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from config import VERSION, PREFIX_PATH, FACES_DIR_PATH, IMAGE_SERVER_PORT
|
||||||
|
from utils import logger
|
||||||
|
from routers import edge_simulation_api
|
||||||
|
|
||||||
|
|
||||||
|
class ImageServer:
|
||||||
|
def __init__(self, app_args):
|
||||||
|
self.port = app_args.port
|
||||||
|
self.app = None
|
||||||
|
self.prepare()
|
||||||
|
self.server_main()
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
"""准备处理,一些适配和配置加载工作"""
|
||||||
|
# 清理图片库,移除不存在数据库中的图片
|
||||||
|
logger.Logger.init("人脸图片库执行初始化校验 ...")
|
||||||
|
for filename in os.listdir(FACES_DIR_PATH):
|
||||||
|
if filename.startswith("_"):
|
||||||
|
os.remove(os.path.join(FACES_DIR_PATH, filename))
|
||||||
|
logger.Logger.init("人脸图片库完成初始化校验 ✅")
|
||||||
|
|
||||||
|
self.app = FastAPI(
|
||||||
|
title="Image Server",
|
||||||
|
description="图片后端服务",
|
||||||
|
version=VERSION,
|
||||||
|
docs_url=f"{PREFIX_PATH}/docs" # 设为None则会关闭
|
||||||
|
)
|
||||||
|
|
||||||
|
# self.app.mount(f"{PREFIX_PATH}/faces", StaticFiles(directory=FACES_DIR_PATH), name="static")
|
||||||
|
self.app.include_router(edge_simulation_api.router, tags=["边缘端请求接口"])
|
||||||
|
|
||||||
|
@self.app.get(f"{PREFIX_PATH}/faces/{{image_filename}}", summary="返回指定图片")
|
||||||
|
async def get_image_data(request: Request, image_filename: str):
|
||||||
|
"""返回指定图片"""
|
||||||
|
image_path = f"{FACES_DIR_PATH}/{image_filename}"
|
||||||
|
if not os.path.exists(image_path):
|
||||||
|
raise HTTPException(status_code=404, detail="Image not found")
|
||||||
|
|
||||||
|
async def image_streamer(file_path: str):
|
||||||
|
async with aiofiles.open(file_path, mode='rb') as f:
|
||||||
|
while chunk := await f.read(1024): # 每次读取 1024 字节
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
logger.Logger.info(f"{PREFIX_PATH}/faces -> {image_path}")
|
||||||
|
return StreamingResponse(image_streamer(image_path), media_type="image/jpeg")
|
||||||
|
|
||||||
|
def server_main(self):
|
||||||
|
"""主服务程序"""
|
||||||
|
uvicorn.run(self.app, host="0.0.0.0", port=self.port, access_log=False)
|
||||||
|
|
||||||
|
|
||||||
|
def main(port: str = IMAGE_SERVER_PORT):
|
||||||
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--port",
|
||||||
|
type=int,
|
||||||
|
default=port,
|
||||||
|
help=f"Port of server, default {logger.new_dc(port)}",
|
||||||
|
)
|
||||||
|
app_args = parser.parse_args()
|
||||||
|
ImageServer(app_args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
41
SCLP/main.py
Normal file
41
SCLP/main.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 主程序运行入口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from config import AUTO_CALLBACK, ENV_FLAG, LAN_IP
|
||||||
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(script_path):
|
||||||
|
# 使用 subprocess 运行脚本
|
||||||
|
subprocess.run(['python', script_path])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logger.Logger.init(f"当前程序环境 ENV_FLAG: {ENV_FLAG}")
|
||||||
|
logger.Logger.init(f"当前环境IP: {LAN_IP}")
|
||||||
|
# 创建两个进程
|
||||||
|
p1 = multiprocessing.Process(target=run_script, args=('backend.py',))
|
||||||
|
p2 = multiprocessing.Process(target=run_script, args=('app.py',))
|
||||||
|
p3 = multiprocessing.Process(target=run_script, args=('image_server.py',))
|
||||||
|
p4 = multiprocessing.Process(target=run_script, args=('auto_callback.py',))
|
||||||
|
|
||||||
|
# 启动进程
|
||||||
|
p1.start()
|
||||||
|
p2.start()
|
||||||
|
p3.start()
|
||||||
|
if AUTO_CALLBACK:
|
||||||
|
p4.start()
|
||||||
|
|
||||||
|
# 等待进程完成
|
||||||
|
p1.join()
|
||||||
|
p2.join()
|
||||||
|
p3.join()
|
||||||
|
if AUTO_CALLBACK:
|
||||||
|
p4.join()
|
||||||
54
SCLP/models/brands.py
Normal file
54
SCLP/models/brands.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 可视对讲厂家信息表 改&查
|
||||||
|
"""
|
||||||
|
from pydantic import BaseModel, field_validator
|
||||||
|
|
||||||
|
from utils.database import BaseTable, get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateBrand(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@field_validator("name")
|
||||||
|
def check_name(cls, value):
|
||||||
|
th = get_table_handler()
|
||||||
|
if BrandsTable.exists(th, value):
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
raise InvalidException("请提供正确的可视对讲厂家名")
|
||||||
|
|
||||||
|
|
||||||
|
class BrandsTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def get_checked_factory(table_handler: BaseTable):
|
||||||
|
"""获取当前启用的可视对讲厂家"""
|
||||||
|
table_handler.query("SELECT brand_name FROM brands WHERE status = '开启'")
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return {"name": res[0][0]}
|
||||||
|
else:
|
||||||
|
return {"name": None}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_checked_factory(table_handler: BaseTable, brand_name: str):
|
||||||
|
"""更新启用的可视对讲厂家"""
|
||||||
|
sqls = [
|
||||||
|
"UPDATE brands SET status = '关闭' WHERE status = '开启'",
|
||||||
|
"UPDATE brands SET status = '开启' WHERE brand_name = ?"
|
||||||
|
]
|
||||||
|
table_handler.execute(sqls, [(), (brand_name,)])
|
||||||
|
return {"status": True}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exists(table_handler: BaseTable, brand_name: str):
|
||||||
|
"""可视对讲厂家是否存在"""
|
||||||
|
table_handler.query("SELECT brand_name FROM brands WHERE brand_name = ?", (brand_name,))
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
793
SCLP/models/devices.py
Normal file
793
SCLP/models/devices.py
Normal file
@ -0,0 +1,793 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 设备信息表 增&改&查
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel, field_validator
|
||||||
|
|
||||||
|
from device.call import ServicesCall, DelFaceItem, AddFaceItem
|
||||||
|
from models.houses import HousesTable
|
||||||
|
from utils import logger
|
||||||
|
from utils.database import BaseTable, get_table_handler
|
||||||
|
from utils.misc import InvalidException, now_datetime_second, decrypt_number
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceRegister(BaseModel):
|
||||||
|
device_mac: str
|
||||||
|
device_name: str
|
||||||
|
device_desc: str
|
||||||
|
third_local_device_id: str
|
||||||
|
device_status: str = "离线"
|
||||||
|
device_sct: Optional[str] = None
|
||||||
|
# 以下需要查询已注册设备信息获取
|
||||||
|
gateway_id: Optional[int] = None
|
||||||
|
gateway_name: Optional[str] = None
|
||||||
|
# 以下需要查询产品信息表获取
|
||||||
|
product_name: str
|
||||||
|
node_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class Device(BaseModel):
|
||||||
|
device_id: int
|
||||||
|
device_name: str
|
||||||
|
node_type: str
|
||||||
|
product_name: str
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDevice(BaseModel):
|
||||||
|
device_id: int
|
||||||
|
building_ids: list[str]
|
||||||
|
|
||||||
|
@field_validator("device_id")
|
||||||
|
def check_device_id(cls, value):
|
||||||
|
th = get_table_handler()
|
||||||
|
if DevicesTable.bind_exits(th, value):
|
||||||
|
raise InvalidException(f"设备:{value} 已被添加过关联")
|
||||||
|
if not DevicesTable.exits(th, value):
|
||||||
|
raise InvalidException(f"设备:{value} 不存在")
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("building_ids")
|
||||||
|
def check_authorized_scope(cls, value):
|
||||||
|
th = get_table_handler()
|
||||||
|
for i in value:
|
||||||
|
if not HousesTable.building_exists(th, i):
|
||||||
|
raise InvalidException(f"楼栋:{i} 不存在")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDevicesScope(BaseModel):
|
||||||
|
device_type: str
|
||||||
|
info: List[AccessDevice]
|
||||||
|
|
||||||
|
@field_validator("device_type")
|
||||||
|
def check_device_type(cls, value):
|
||||||
|
if value in ["大门闸机", "大门门禁"]:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
raise InvalidException("请提供正确的设备类型:[大门闸机, 大门门禁]")
|
||||||
|
|
||||||
|
|
||||||
|
class BuildingDevice(BaseModel):
|
||||||
|
device_id: int
|
||||||
|
bind_unit_id: str
|
||||||
|
|
||||||
|
@field_validator("device_id")
|
||||||
|
def check_device_id(cls, value):
|
||||||
|
th = get_table_handler()
|
||||||
|
if DevicesTable.bind_exits(th, value):
|
||||||
|
raise InvalidException(f"设备:{value} 已被添加过关联")
|
||||||
|
if not DevicesTable.exits(th, value):
|
||||||
|
raise InvalidException(f"设备:{value} 不存在")
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("bind_unit_id")
|
||||||
|
def check_bind_unit_id(cls, value):
|
||||||
|
th = get_table_handler()
|
||||||
|
if not HousesTable.unit_exists(th, value):
|
||||||
|
raise InvalidException(f"单元:{value} 不存在")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class BuildingDevicesScope(BaseModel):
|
||||||
|
info: List[BuildingDevice]
|
||||||
|
|
||||||
|
|
||||||
|
class SearchDevicesInfo(BaseModel):
|
||||||
|
search_type: Optional[str] = None
|
||||||
|
search_key: Optional[str] = None
|
||||||
|
|
||||||
|
@field_validator("search_type")
|
||||||
|
def check_search_type(cls, value):
|
||||||
|
types = {
|
||||||
|
"设备名称": "device_name",
|
||||||
|
"MAC地址": "device_mac",
|
||||||
|
"设备ID": "device_id"
|
||||||
|
}
|
||||||
|
if value in types:
|
||||||
|
return types[value]
|
||||||
|
else:
|
||||||
|
raise InvalidException(f"请提供正确的类型:{list(types.keys())}")
|
||||||
|
|
||||||
|
|
||||||
|
class DevicesTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def check(table_handler: BaseTable):
|
||||||
|
"""检测是否存在当前表"""
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE devices (
|
||||||
|
device_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
device_mac TEXT UNIQUE,
|
||||||
|
device_name TEXT,
|
||||||
|
device_desc TEXT,
|
||||||
|
third_local_device_id TEXT,
|
||||||
|
device_status TEXT,
|
||||||
|
device_sct TEXT,
|
||||||
|
gateway_id INTEGER,
|
||||||
|
gateway_name TEXT,
|
||||||
|
product_name TEXT,
|
||||||
|
node_type TEXT,
|
||||||
|
last_online_datetime TEXT,
|
||||||
|
register_datetime TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/devices.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 12:
|
||||||
|
for row in csvreader:
|
||||||
|
device_mac = row[0].strip()
|
||||||
|
device_name = row[1].strip()
|
||||||
|
device_desc = row[2].strip() if row[2].strip() else None
|
||||||
|
third_local_device_id = row[3].strip() if row[3].strip() else None
|
||||||
|
device_status = row[4].strip() if row[4].strip() else "离线"
|
||||||
|
device_sct = row[5].strip() if row[5].strip() else None
|
||||||
|
gateway_id = int(row[6].strip()) if row[6].strip() else None
|
||||||
|
gateway_name = row[7].strip() if row[7].strip() else None
|
||||||
|
product_name = row[8].strip()
|
||||||
|
node_type = row[9].strip()
|
||||||
|
last_online_datetime = row[10].strip() if row[10].strip() else None
|
||||||
|
register_datetime = row[11].strip() if row[11].strip() else None
|
||||||
|
data.append((device_mac, device_name, device_desc, third_local_device_id, device_status,
|
||||||
|
device_sct, gateway_id, gateway_name, product_name, node_type,
|
||||||
|
last_online_datetime, register_datetime))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO devices
|
||||||
|
(device_mac, device_name, device_desc, third_local_device_id, device_status,
|
||||||
|
device_sct, gateway_id, gateway_name, product_name, node_type,
|
||||||
|
last_online_datetime, register_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (device_mac) DO NOTHING
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_scope'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE devices_scope (
|
||||||
|
device_id INT,
|
||||||
|
device_type TEXT,
|
||||||
|
bind_unit_id TEXT,
|
||||||
|
UNIQUE (device_id, bind_unit_id)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
|
||||||
|
"data/InitialData/devices_scope.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 3:
|
||||||
|
for row in csvreader:
|
||||||
|
device_id = row[0].strip()
|
||||||
|
device_type = row[1].strip()
|
||||||
|
bind_unit_id = row[2].strip()
|
||||||
|
data.append((device_id, device_type, bind_unit_id))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO devices_scope
|
||||||
|
(device_id, device_type, bind_unit_id)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, bind_unit_id) DO NOTHING
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='devices_auth'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE devices_auth (
|
||||||
|
device_id INT,
|
||||||
|
_id TEXT,
|
||||||
|
record_type TEXT,
|
||||||
|
start_date TEXT,
|
||||||
|
expire_date TEXT,
|
||||||
|
add_datetime TEXT,
|
||||||
|
update_datetime TEXT,
|
||||||
|
UNIQUE (device_id, _id, record_type)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")),
|
||||||
|
"data/InitialData/devices_auth.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 5:
|
||||||
|
for row in csvreader:
|
||||||
|
device_id = row[0].strip()
|
||||||
|
_id = row[1].strip()
|
||||||
|
record_type = row[2].strip()
|
||||||
|
start_date = row[3].strip()
|
||||||
|
expire_date = row[4].strip()
|
||||||
|
data.append((device_id, _id, record_type, start_date, expire_date,
|
||||||
|
now_datetime_second(), now_datetime_second()))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO devices_auth
|
||||||
|
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, _id, record_type) DO NOTHING
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def insert_by_device_register(table_handler: BaseTable, obj: DeviceRegister):
|
||||||
|
register_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO devices
|
||||||
|
(device_mac, device_name, device_desc, third_local_device_id, device_status, device_sct,
|
||||||
|
gateway_id, gateway_name, product_name, node_type, register_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (device_mac) DO UPDATE SET
|
||||||
|
device_name=?, device_desc=?, third_local_device_id=?,
|
||||||
|
device_status=?, device_sct=?, gateway_id=?, gateway_name=?,
|
||||||
|
product_name=?, node_type=?, register_datetime=?
|
||||||
|
""",
|
||||||
|
(obj.device_mac, obj.device_name, obj.device_desc, obj.third_local_device_id,
|
||||||
|
obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name,
|
||||||
|
obj.product_name, obj.node_type, register_datetime,
|
||||||
|
obj.device_name, obj.device_desc, obj.third_local_device_id,
|
||||||
|
obj.device_status, obj.device_sct, obj.gateway_id, obj.gateway_name,
|
||||||
|
obj.product_name, obj.node_type, register_datetime)
|
||||||
|
)
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_id
|
||||||
|
FROM devices
|
||||||
|
WHERE device_mac = ?
|
||||||
|
""",
|
||||||
|
(obj.device_mac,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def insert_device_auth_record(table_handler: BaseTable, device_id: int, _id: str,
|
||||||
|
start_date: str, expire_date: str, record_type: str = '人行'):
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO devices_auth
|
||||||
|
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, _id, record_type) DO UPDATE SET
|
||||||
|
start_date=?, expire_date=?, update_datetime=?
|
||||||
|
""",
|
||||||
|
(device_id, _id, record_type, start_date, expire_date,
|
||||||
|
now_datetime_second(), now_datetime_second(), start_date, expire_date, now_datetime_second())
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_devices_info(table_handler: BaseTable):
|
||||||
|
"""获取对应设备的基本信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_name, third_local_device_id, device_id, device_status,
|
||||||
|
gateway_name, product_name, node_type, last_online_datetime, register_datetime
|
||||||
|
FROM devices
|
||||||
|
WHERE node_type != '网关设备'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
devices_info = []
|
||||||
|
for item in res:
|
||||||
|
devices_info.append({
|
||||||
|
"device_name": item[0],
|
||||||
|
"third_local_device_id": item[1],
|
||||||
|
"device_id": item[2],
|
||||||
|
"device_status": item[3],
|
||||||
|
"gateway_name": item[4] if item[4] else "",
|
||||||
|
"product_name": item[5],
|
||||||
|
"node_type": item[6],
|
||||||
|
"last_online_datetime": item[7] if item[7] else "",
|
||||||
|
"register_datetime": item[8]
|
||||||
|
})
|
||||||
|
return devices_info
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_info(table_handler: BaseTable, device_id: int):
|
||||||
|
"""获取对应设备的基本信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_name, node_type, product_name
|
||||||
|
FROM devices
|
||||||
|
WHERE device_id = ?
|
||||||
|
""",
|
||||||
|
(device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return Device(device_id=device_id, device_name=res[0][0], node_type=res[0][1], product_name=res[0][2])
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_name(table_handler: BaseTable, device_id: int):
|
||||||
|
"""获取对应设备名"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_name
|
||||||
|
FROM devices
|
||||||
|
WHERE device_id = ?
|
||||||
|
""",
|
||||||
|
(device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_scope_type(table_handler: BaseTable, device_id: int):
|
||||||
|
"""获取对应设备门禁类型"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_type
|
||||||
|
FROM devices_scope
|
||||||
|
WHERE device_id = ?
|
||||||
|
""",
|
||||||
|
(device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_ids_by_unit_id(table_handler: BaseTable, unit_id: str) -> list:
|
||||||
|
"""查询对应单元权限内的设备ID"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT GROUP_CONCAT(device_id) AS device_ids
|
||||||
|
FROM devices_scope
|
||||||
|
WHERE bind_unit_id = ?
|
||||||
|
""", (unit_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0].split(',')
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_device_ids(table_handler: BaseTable, filter_name: Optional[str] = None, filter_online: bool = True):
|
||||||
|
"""获取相关的设备ID"""
|
||||||
|
sub_sql_list = []
|
||||||
|
if filter_online:
|
||||||
|
sub_sql_list.append("device_status = '在线'")
|
||||||
|
if filter_name is not None:
|
||||||
|
sub_sql_list.append(f"product_name like '%{filter_name}%'")
|
||||||
|
if len(sub_sql_list) > 0:
|
||||||
|
sub_sql = "WHERE " + " AND ".join(sub_sql_list)
|
||||||
|
else:
|
||||||
|
sub_sql = ""
|
||||||
|
table_handler.query(f"SELECT GROUP_CONCAT(device_id) device_ids FROM devices {sub_sql}")
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return [i for i in res[0][0].split(',')]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_auth_interval(table_handler: BaseTable, _id: str, device_id: int, record_type: str = '人行'):
|
||||||
|
"""获取对应ID授权在设备上的有效期"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT start_date, expire_date
|
||||||
|
FROM devices_auth
|
||||||
|
WHERE device_id = ? and _id = ? and record_type = ?
|
||||||
|
""",
|
||||||
|
(device_id, _id, record_type)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0], res[0][1]
|
||||||
|
return "", ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_device_status(table_handler: BaseTable, device_id: int, device_status: str):
|
||||||
|
"""更新设备在线状态"""
|
||||||
|
if device_status == "在线":
|
||||||
|
last_online_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
set_sql = f"device_status='在线', last_online_datetime='{last_online_datetime}' "
|
||||||
|
else:
|
||||||
|
set_sql = f"device_status='离线' "
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE devices SET {set_sql}
|
||||||
|
WHERE device_id={device_id}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def offline_gateway(table_handler: BaseTable, device_id: int):
|
||||||
|
"""用于在网关下线时同时下线网关下的所有设备"""
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
UPDATE devices SET device_status='离线'
|
||||||
|
WHERE device_id={device_id} or gateway_id={device_id}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_access_devices_info(table_handler: BaseTable,
|
||||||
|
is_associated: bool,
|
||||||
|
device_name: str = None,
|
||||||
|
product_name: str = None,
|
||||||
|
device_mac: str = None,
|
||||||
|
device_id: int = None):
|
||||||
|
if is_associated:
|
||||||
|
sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is not null "
|
||||||
|
else:
|
||||||
|
sub_sql = "WHERE d.node_type != '网关设备' AND product_name not like '%停车场%' AND device_type is null "
|
||||||
|
if device_name:
|
||||||
|
sub_sql += f"AND device_name = '{device_name}'"
|
||||||
|
elif product_name:
|
||||||
|
sub_sql += f"AND product_name = '{product_name}'"
|
||||||
|
elif device_mac:
|
||||||
|
sub_sql += f"AND device_mac = '{device_mac}'"
|
||||||
|
elif device_id:
|
||||||
|
sub_sql += f"AND d.device_id = {device_id}"
|
||||||
|
table_handler.query(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
d.device_id,
|
||||||
|
device_name,
|
||||||
|
device_mac,
|
||||||
|
device_status,
|
||||||
|
ds.device_type,
|
||||||
|
GROUP_CONCAT(t.building_name || '-' || t.unit_name) as scopes,
|
||||||
|
product_name
|
||||||
|
FROM devices d
|
||||||
|
LEFT JOIN devices_scope ds ON ds.device_id = d.device_id
|
||||||
|
LEFT JOIN (SELECT DISTINCT unit_id, unit_name, building_id, building_name FROM houses) t
|
||||||
|
ON t.unit_id = ds.bind_unit_id
|
||||||
|
{sub_sql}
|
||||||
|
GROUP BY d.device_id, device_name, device_mac, device_status, ds.device_type, product_name
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
devices_info = []
|
||||||
|
for i in res:
|
||||||
|
devices_info.append({
|
||||||
|
"device_id": i[0],
|
||||||
|
"device_name": i[1],
|
||||||
|
"device_mac": i[2],
|
||||||
|
"device_status": i[3],
|
||||||
|
"device_type": i[4] if i[4] else "",
|
||||||
|
"authorized_scope": i[5].split(",") if i[5] else [],
|
||||||
|
"product_name": i[6]
|
||||||
|
})
|
||||||
|
return {"devices": devices_info}
|
||||||
|
return {"devices": []}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_associated_access_devices_info(table_handler: BaseTable,
|
||||||
|
search_type: Optional[str] = None,
|
||||||
|
search_key: Optional[str] = None):
|
||||||
|
if search_type:
|
||||||
|
if search_type == "device_id":
|
||||||
|
sub_sql = f"WHERE ds.device_id = {search_key}"
|
||||||
|
elif search_type == "device_name":
|
||||||
|
sub_sql = f"WHERE d.device_name like '%{search_key}%'"
|
||||||
|
else:
|
||||||
|
sub_sql = f"WHERE d.{search_type} = '{search_key}'"
|
||||||
|
else:
|
||||||
|
sub_sql = ""
|
||||||
|
table_handler.query(
|
||||||
|
f"""
|
||||||
|
SELECT ds.device_id as _id,
|
||||||
|
d.device_name as _name,
|
||||||
|
d.device_mac as _mac,
|
||||||
|
d.device_status as _status
|
||||||
|
FROM (SELECT DISTINCT device_id FROM devices_scope) ds
|
||||||
|
LEFT JOIN devices d ON ds.device_id = d.device_id
|
||||||
|
{sub_sql}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
devices_info = []
|
||||||
|
for i in res:
|
||||||
|
devices_info.append({
|
||||||
|
"device_id": i[0],
|
||||||
|
"device_name": i[1],
|
||||||
|
"device_mac": i[2],
|
||||||
|
"device_status": i[3]
|
||||||
|
})
|
||||||
|
return {"devices": devices_info}
|
||||||
|
return {"devices": []}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def auto_auth_by_unit_id(table_handler: BaseTable, device_id: int, unit_id: str):
|
||||||
|
"""查询对应单元下相关住户,对具备人脸的住户授权"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT h.householder_id, name, phone, face_url
|
||||||
|
FROM householders h
|
||||||
|
LEFT JOIN householders_type ht ON ht.householder_id=h.householder_id
|
||||||
|
LEFT JOIN houses ON ht.room_id=houses.room_id
|
||||||
|
WHERE h.type = '住户' AND (face_url != '' or face_url is not null ) AND houses.unit_id = ?
|
||||||
|
""", (unit_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
# 人员人脸下放
|
||||||
|
for i in res:
|
||||||
|
sc = ServicesCall()
|
||||||
|
face_item = AddFaceItem(
|
||||||
|
user_id=i[0],
|
||||||
|
name=i[1],
|
||||||
|
phone_number=decrypt_number(i[2]),
|
||||||
|
face_url=i[3],
|
||||||
|
device_ids=str([device_id])
|
||||||
|
)
|
||||||
|
_callback, code, msg = sc.add_face(device_id, face_item)
|
||||||
|
if _callback:
|
||||||
|
DevicesTable.insert_device_auth_record(table_handler, device_id, face_item.user_id,
|
||||||
|
face_item.start_date, face_item.expire_date)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_access_devices(table_handler: BaseTable, device_type: str, objs: List[AccessDevice]):
|
||||||
|
data = []
|
||||||
|
for obj in objs:
|
||||||
|
for bind_building_id in obj.building_ids:
|
||||||
|
unit_ids = HousesTable.get_unit_ids(table_handler, bind_building_id)
|
||||||
|
for unit_id in unit_ids:
|
||||||
|
data.append((obj.device_id, device_type, unit_id, device_type))
|
||||||
|
DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, unit_id)
|
||||||
|
try:
|
||||||
|
table_handler.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO devices_scope
|
||||||
|
(device_id, device_type, bind_unit_id)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, bind_unit_id) DO UPDATE
|
||||||
|
SET device_type = ?
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}")
|
||||||
|
raise InvalidException(f"DevicesTable.add_access_devices: {type(e).__name__}, {e}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_building_devices(table_handler: BaseTable, device_type: str, objs: List[BuildingDevice]):
|
||||||
|
data = []
|
||||||
|
for obj in objs:
|
||||||
|
data.append((obj.device_id, device_type, obj.bind_unit_id))
|
||||||
|
DevicesTable.auto_auth_by_unit_id(table_handler, obj.device_id, obj.bind_unit_id)
|
||||||
|
try:
|
||||||
|
table_handler.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO devices_scope
|
||||||
|
(device_id, device_type, bind_unit_id)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, bind_unit_id) DO NOTHING
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"DevicesTable.add_building_devices: {type(e).__name__}, {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_update_device_auth(table_handler: BaseTable, device_id, _ids: list[str], record_type: str,
|
||||||
|
start_date: str, expire_date: str):
|
||||||
|
"""对应设备批量授权记录增加"""
|
||||||
|
data = []
|
||||||
|
for _id in _ids:
|
||||||
|
_datetime = now_datetime_second()
|
||||||
|
data.append((device_id, _id, record_type, start_date, expire_date, _datetime, _datetime,
|
||||||
|
start_date, expire_date, _datetime))
|
||||||
|
try:
|
||||||
|
table_handler.executemany(
|
||||||
|
"""
|
||||||
|
INSERT INTO devices_auth
|
||||||
|
(device_id, _id, record_type, start_date, expire_date, add_datetime, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (device_id, _id, record_type)
|
||||||
|
DO UPDATE SET start_date = ?, expire_date = ?, update_datetime = ?
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"DevicesTable.add_device_auth: {type(e).__name__}, {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_auth_device_ids(table_handler: BaseTable, _id: str, record_type: str = '人行'):
|
||||||
|
"""获取ID的所有授权设备"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_id
|
||||||
|
FROM devices_auth
|
||||||
|
WHERE _id = ? AND record_type = ?
|
||||||
|
""", (_id, record_type)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return [i[0] for i in res]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_auth_device_info(table_handler: BaseTable, _id: str, record_type: str = '人行'):
|
||||||
|
"""获取ID当前所有授权设备基础信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT devices_auth.device_id, device_name, device_mac, device_status
|
||||||
|
FROM devices_auth
|
||||||
|
LEFT JOIN devices ON devices.device_id = devices_auth.device_id
|
||||||
|
WHERE _id = ? AND record_type = ?
|
||||||
|
""", (_id, record_type)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return [{"device_id": i[0], "device_name": i[1], "device_mac": i[2], "device_status": i[3]} for i in res]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_auth_householders_info(table_handler: BaseTable, device_id: int):
|
||||||
|
"""获取关联授权下的所有完成下放的人员信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT da._id, name, phone, face_url, da.start_date, da.expire_date
|
||||||
|
FROM devices_auth da
|
||||||
|
LEFT JOIN householders h ON da._id = h.householder_id
|
||||||
|
WHERE device_id = ? AND record_type = '人行'
|
||||||
|
""", (device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return [{"id": i[0], "name": i[1], "phone": i[2], "url": i[3], "sdate": i[4], "edate": i[5]} for i in res]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_householder_all_auth(table_handler: BaseTable, _id: str):
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM devices_auth
|
||||||
|
WHERE _id = ? AND record_type = '人行'
|
||||||
|
""", (_id,)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_invalid_auth_record(table_handler: BaseTable, device_id: int, _id: str, record_type: str = '人行'):
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM devices_auth
|
||||||
|
WHERE device_id = ? AND _id = ? AND record_type = ?
|
||||||
|
""",
|
||||||
|
(device_id, _id, record_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_access_device_info(table_handler: BaseTable, device_id: int):
|
||||||
|
# 1. 确认设备ID有效
|
||||||
|
if not DevicesTable.exits(table_handler, device_id):
|
||||||
|
raise InvalidException(f"设备:{device_id} 不存在")
|
||||||
|
# 2. 获取关联授权下的所有完成下放的人员信息
|
||||||
|
householder_infos = DevicesTable.get_auth_householders_info(table_handler, device_id)
|
||||||
|
if len(householder_infos) > 0:
|
||||||
|
# 3. 移除对应设备中所有的人员信息
|
||||||
|
sc = ServicesCall()
|
||||||
|
success_ids = []
|
||||||
|
for index, householder_info in enumerate(householder_infos):
|
||||||
|
_callback, code, _ = sc.del_face(device_id,
|
||||||
|
DelFaceItem(user_id=str(householder_info["id"]),
|
||||||
|
device_ids=str([device_id])))
|
||||||
|
if not _callback:
|
||||||
|
# 存在异常时,回滚已删除掉的人脸
|
||||||
|
failed = []
|
||||||
|
msgs = []
|
||||||
|
for success_id in success_ids:
|
||||||
|
_back, code, msg = sc.add_face(device_id, AddFaceItem(
|
||||||
|
name=householder_infos[success_id]["name"],
|
||||||
|
user_id=str(householder_infos[success_id]["id"]),
|
||||||
|
phone_number=householder_infos[success_id]["phone"],
|
||||||
|
face_url=householder_infos[success_id]["url"],
|
||||||
|
device_ids=str([device_id]),
|
||||||
|
start_date=householder_infos[success_id]["sdate"],
|
||||||
|
expire_date=householder_infos[success_id]["edate"]
|
||||||
|
))
|
||||||
|
if not _back:
|
||||||
|
failed.append(householder_infos[success_id]["id"])
|
||||||
|
msgs.append(msg)
|
||||||
|
if len(failed) == 0:
|
||||||
|
raise InvalidException(
|
||||||
|
f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 已完成回滚")
|
||||||
|
else:
|
||||||
|
raise InvalidException(
|
||||||
|
f"住户 - {householder_info['id']} 人脸授权移除失败,错误码{code}, {msgs}, 回滚失败")
|
||||||
|
success_ids.append(index)
|
||||||
|
# 4. 移除设备的关联记录
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM devices_auth
|
||||||
|
WHERE device_id = ? AND record_type = '人行'
|
||||||
|
""", (device_id,)
|
||||||
|
)
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM devices_scope
|
||||||
|
WHERE device_id = ?
|
||||||
|
""", (device_id,)
|
||||||
|
)
|
||||||
|
return {"status": True}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exits(table_handler: BaseTable, device_id: int):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT device_id FROM devices
|
||||||
|
WHERE device_id = ?
|
||||||
|
""", (device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bind_exits(table_handler: BaseTable, device_id: int):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT bind_unit_id
|
||||||
|
FROM devices_scope
|
||||||
|
WHERE device_id = ?
|
||||||
|
""", (device_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
1369
SCLP/models/householders.py
Normal file
1369
SCLP/models/householders.py
Normal file
File diff suppressed because it is too large
Load Diff
303
SCLP/models/houses.py
Normal file
303
SCLP/models/houses.py
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 房产信息表查&改
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from utils import logger
|
||||||
|
from utils.database import BaseTable
|
||||||
|
from utils.misc import now_datetime_second, extract_fixed_length_number
|
||||||
|
|
||||||
|
|
||||||
|
class HousesTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def check(table_handler: BaseTable):
|
||||||
|
"""检测是否存在当前表"""
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='houses'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE houses (
|
||||||
|
room_id TEXT UNIQUE,
|
||||||
|
house_name TEXT UNIQUE,
|
||||||
|
project_id TEXT,
|
||||||
|
project_name TEXT,
|
||||||
|
area_id TEXT,
|
||||||
|
area_name TEXT,
|
||||||
|
building_id TEXT,
|
||||||
|
building_name TEXT,
|
||||||
|
unit_id TEXT,
|
||||||
|
unit_name TEXT,
|
||||||
|
floor_id TEXT,
|
||||||
|
floor_name TEXT,
|
||||||
|
room_name TEXT,
|
||||||
|
householder_count INTEGER,
|
||||||
|
update_datetime TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# 根据导出数据生成内置数据
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/houses.csv")
|
||||||
|
csv_file = open(init_config_path, "w", encoding="utf8")
|
||||||
|
csv_file.write(
|
||||||
|
"房屋ID, 房屋编号, 项目ID, 所属项目, 区域ID, 所属区域, 楼栋ID, 所属楼栋, 单元ID, 所属单元, 楼层ID, 所属楼层, 房间号, 住户数量\n")
|
||||||
|
room_info_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/ExportData/room.txt")
|
||||||
|
if os.path.exists(room_info_path):
|
||||||
|
with open(room_info_path, "r", encoding="utf8") as f:
|
||||||
|
file_content = f.read()
|
||||||
|
file_content = file_content.replace("ISODate(", "").replace(")", "").replace("'", "\"")
|
||||||
|
room_info = json.loads(file_content)
|
||||||
|
for item in room_info:
|
||||||
|
room_id = item.get("_id", "")
|
||||||
|
project_id = item["project_id"]
|
||||||
|
project = item["project_name"]
|
||||||
|
area_id = item.get("area_id", "")
|
||||||
|
area = item.get("area_name", "")
|
||||||
|
building_id = item.get("build_id", "")
|
||||||
|
building = item.get("building_name", "")
|
||||||
|
unit_id = item.get("unit_id", "")
|
||||||
|
unit = item.get("unit_name", "")
|
||||||
|
floor_id = item.get("floor_id", "")
|
||||||
|
floor = item.get("floor_name", "")
|
||||||
|
room = item.get("num", "")
|
||||||
|
wait_write = [room_id,
|
||||||
|
'-'.join([i for i in [project, area, building, unit, floor, room] if i != '']),
|
||||||
|
project_id, project, area_id, area, building_id, building,
|
||||||
|
unit_id, unit, floor_id, floor, room, str(len(item.get("households", [])))]
|
||||||
|
csv_file.write(', '.join(wait_write) + "\n")
|
||||||
|
csv_file.flush()
|
||||||
|
csv_file.close()
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 14:
|
||||||
|
for row in csvreader:
|
||||||
|
room_id = row[0].strip()
|
||||||
|
house_name = row[1].strip()
|
||||||
|
project_id = row[2].strip()
|
||||||
|
project_name = row[3].strip()
|
||||||
|
area_id = row[4].strip() if row[4].strip() else None
|
||||||
|
area_name = row[5].strip() if row[5].strip() else None
|
||||||
|
building_id = row[6].strip() if row[6].strip() else None
|
||||||
|
building_name = row[7].strip() if row[7].strip() else None
|
||||||
|
unit_id = row[8].strip() if row[8].strip() else None
|
||||||
|
unit_name = row[9].strip() if row[9].strip() else None
|
||||||
|
floor_id = row[10].strip() if row[10].strip() else None
|
||||||
|
floor_name = row[11].strip() if row[11].strip() else None
|
||||||
|
room_name = row[12].strip()
|
||||||
|
householder_count = int(row[13].strip())
|
||||||
|
update_datetime = now_datetime_second()
|
||||||
|
data.append((room_id, house_name, project_id, project_name, area_id, area_name,
|
||||||
|
building_id, building_name, unit_id, unit_name, floor_id, floor_name,
|
||||||
|
room_name, householder_count, update_datetime))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO houses
|
||||||
|
(room_id, house_name, project_id, project_name, area_id, area_name,
|
||||||
|
building_id, building_name, unit_id, unit_name, floor_id, floor_name,
|
||||||
|
room_name, householder_count, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (room_id) DO NOTHING
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_house_detail_info(table_handler: BaseTable, room_id):
|
||||||
|
"""根据房屋ID获取房产详细信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT room_id, project_id, room_name, project_name
|
||||||
|
FROM houses
|
||||||
|
WHERE room_id = ?
|
||||||
|
""", (room_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return {
|
||||||
|
"id": res[0][0],
|
||||||
|
"project_id": res[0][1],
|
||||||
|
"name": res[0][2],
|
||||||
|
"type": 1,
|
||||||
|
"subtype": 0,
|
||||||
|
"project_name": res[0][3]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unit_id_by_room_id(table_handler: BaseTable, room_id):
|
||||||
|
"""根据房屋ID获取房产详细信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT unit_id
|
||||||
|
FROM houses
|
||||||
|
WHERE room_id = ?
|
||||||
|
""", (room_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_house_ty_info(table_handler: BaseTable, project_id: str, room_id: str) -> dict | None:
|
||||||
|
"""查询房产真实信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT houses.room_id, house_name, project_id, corp_id,
|
||||||
|
building_name, unit_name, floor_name, room_name
|
||||||
|
FROM houses
|
||||||
|
LEFT JOIN subsystem ON houses.room_id = subsystem.room_id
|
||||||
|
WHERE project_id = ? AND houses.room_id = ?
|
||||||
|
""", (project_id, room_id)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
building = extract_fixed_length_number(res[0][4])
|
||||||
|
unit = extract_fixed_length_number(res[0][5])
|
||||||
|
floor = extract_fixed_length_number(res[0][6])
|
||||||
|
room = extract_fixed_length_number(res[0][7])
|
||||||
|
ty_house_id = f"{building}{unit}{floor}{room}"
|
||||||
|
return {
|
||||||
|
"id": res[0][0],
|
||||||
|
"house_name": res[0][1],
|
||||||
|
"ty_house_id": ty_house_id,
|
||||||
|
"project_id": res[0][2],
|
||||||
|
"corp_id": res[0][3]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_items_by_dynamic_item(table_handler: BaseTable, query_target: str, query_item: dict):
|
||||||
|
sub_sql_list = []
|
||||||
|
for key, value in query_item.items():
|
||||||
|
if isinstance(value, int):
|
||||||
|
sub_sql_list.append(f"{key}_id = {value}")
|
||||||
|
else:
|
||||||
|
sub_sql_list.append(f"{key}_id = '{value}'")
|
||||||
|
sub_sql = "" if len(sub_sql_list) == 0 else "WHERE " + " and ".join(sub_sql_list)
|
||||||
|
try:
|
||||||
|
table_handler.query(
|
||||||
|
f"""
|
||||||
|
SELECT DISTINCT {query_target}_id, {query_target}_name
|
||||||
|
FROM houses {sub_sql}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
item_list = [{"_id": i[0], "_name": i[1]} for i in res if i[0] is not None]
|
||||||
|
return item_list
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.Logger.error(f"{type(e).__name__}, {e}")
|
||||||
|
if logger.DEBUG:
|
||||||
|
traceback.print_exc()
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_householder_count(table_handler: BaseTable, room_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT householder_count
|
||||||
|
FROM houses
|
||||||
|
WHERE room_id = ?
|
||||||
|
""",
|
||||||
|
(room_id,),
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_householder_count(table_handler: BaseTable, room_id: str, householder_count: int):
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
UPDATE houses SET
|
||||||
|
householder_count=?,
|
||||||
|
update_datetime=?
|
||||||
|
WHERE room_id=?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
householder_count,
|
||||||
|
now_datetime_second(),
|
||||||
|
room_id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_unit_ids(table_handler: BaseTable, building_id: str):
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
SELECT DISTINCT unit_id
|
||||||
|
FROM houses
|
||||||
|
WHERE building_id = ?
|
||||||
|
""", (building_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return [i[0] for i in res]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exists(table_handler: BaseTable, room_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT house_name
|
||||||
|
FROM houses
|
||||||
|
WHERE room_id = ?
|
||||||
|
""",
|
||||||
|
(room_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def building_exists(table_handler: BaseTable, building_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT DISTINCT building_id
|
||||||
|
FROM houses
|
||||||
|
WHERE building_id = ?
|
||||||
|
""",
|
||||||
|
(building_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unit_exists(table_handler: BaseTable, unit_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT DISTINCT unit_id
|
||||||
|
FROM houses
|
||||||
|
WHERE unit_id = ?
|
||||||
|
""",
|
||||||
|
(unit_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
1019
SCLP/models/parkinglots.py
Normal file
1019
SCLP/models/parkinglots.py
Normal file
File diff suppressed because it is too large
Load Diff
113
SCLP/models/products.py
Normal file
113
SCLP/models/products.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 内置的产品信息表,在初始化时会初始化csv进行覆盖
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from utils import logger
|
||||||
|
from utils.database import BaseTable
|
||||||
|
from utils.misc import now_datetime_second
|
||||||
|
|
||||||
|
|
||||||
|
class Product(BaseModel):
|
||||||
|
product_id: str
|
||||||
|
product_name: str
|
||||||
|
node_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class ProductsTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def check(table_handler: BaseTable):
|
||||||
|
"""检测是否存在当前表"""
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/products.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
table_handler.execute("DROP TABLE IF EXISTS products;")
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE products (
|
||||||
|
product_id TEXT,
|
||||||
|
product_name TEXT,
|
||||||
|
node_type TEXT,
|
||||||
|
update_datetime TEXT,
|
||||||
|
PRIMARY KEY (product_id)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 3:
|
||||||
|
for row in csvreader:
|
||||||
|
product_id = row[0].strip()
|
||||||
|
product_name = row[1].strip()
|
||||||
|
node_type = row[2].strip()
|
||||||
|
update_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
data.append((product_id, product_name, node_type, update_datetime))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO products
|
||||||
|
(product_id, product_name, node_type, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='brands'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE brands (
|
||||||
|
brand_id INT,
|
||||||
|
brand_name TEXT,
|
||||||
|
status TEXT,
|
||||||
|
add_datetime TEXT,
|
||||||
|
update_datetime TEXT,
|
||||||
|
PRIMARY KEY (brand_id)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/brands.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
data = []
|
||||||
|
if len(head) == 3:
|
||||||
|
for row in csvreader:
|
||||||
|
brand_id = row[0].strip()
|
||||||
|
brand_name = row[1].strip()
|
||||||
|
status = row[2].strip()
|
||||||
|
data.append((brand_id, brand_name, status, now_datetime_second(), now_datetime_second()))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO brands
|
||||||
|
(brand_id, brand_name, status, add_datetime, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (brand_id) DO NOTHING
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_product_info(table_handler: BaseTable, product_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT product_name, node_type
|
||||||
|
FROM products
|
||||||
|
WHERE product_id = ?
|
||||||
|
""",
|
||||||
|
(product_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return Product(product_id=product_id, product_name=res[0][0], node_type=res[0][1])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
113
SCLP/models/sessions.py
Normal file
113
SCLP/models/sessions.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 记录一些会话数据
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
|
||||||
|
from utils.database import BaseTable
|
||||||
|
|
||||||
|
|
||||||
|
class SessionsTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def check(table_handler: BaseTable):
|
||||||
|
"""检测是否存在当前表"""
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE sessions (
|
||||||
|
session_id TEXT,
|
||||||
|
username TEXT,
|
||||||
|
token TEXT,
|
||||||
|
captcha TEXT,
|
||||||
|
last_timestamp TEXT,
|
||||||
|
PRIMARY KEY (session_id)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE INDEX idx_sessions_token ON sessions(token);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# 去除冗余的验证会话信息
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
DELETE FROM sessions WHERE last_timestamp < {int(time.time() * 1000) - 3 * 24 * 60 * 60 * 1000}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_captcha(table_handler: BaseTable, session_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT captcha
|
||||||
|
FROM sessions
|
||||||
|
WHERE session_id = ?
|
||||||
|
""",
|
||||||
|
(session_id,),
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def insert(table_handler: BaseTable, session_id, captcha):
|
||||||
|
timestamp = str(int(time.time() * 1000))
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO sessions
|
||||||
|
(session_id, captcha, last_timestamp)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT (session_id)
|
||||||
|
DO UPDATE SET captcha=?, last_timestamp=?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
session_id,
|
||||||
|
captcha,
|
||||||
|
timestamp,
|
||||||
|
captcha,
|
||||||
|
timestamp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update(table_handler: BaseTable, session_id: str, username: str, token: str):
|
||||||
|
timestamp = str(int(time.time() * 1000))
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
UPDATE sessions SET
|
||||||
|
username=?,
|
||||||
|
token=?,
|
||||||
|
last_timestamp=?
|
||||||
|
WHERE session_id=?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
username,
|
||||||
|
token,
|
||||||
|
timestamp,
|
||||||
|
session_id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_token(table_handler: BaseTable, timestamp: str, token: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT captcha
|
||||||
|
FROM sessions
|
||||||
|
WHERE last_timestamp > ? and token = ?
|
||||||
|
""",
|
||||||
|
(timestamp, token),
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
95
SCLP/models/spaces.py
Normal file
95
SCLP/models/spaces.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 子系统映射表 增&改&查
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
from models.houses import HousesTable
|
||||||
|
from utils.database import BaseTable
|
||||||
|
from utils.misc import now_datetime_second, sql_export_xls
|
||||||
|
|
||||||
|
|
||||||
|
class SpacesTable(BaseTable):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_sub_system_name_by_room_id(table_handler: BaseTable, room_id: str):
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT sub_space_name
|
||||||
|
FROM subsystem
|
||||||
|
WHERE room_id = ?
|
||||||
|
""", (room_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return res[0][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_space_info_by_sub_space_name(table_handler: BaseTable, sub_space_name: str):
|
||||||
|
"""根据第三方ID获取对应的房产信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT subsystem.room_id, house_name, project_id, sub_system_id
|
||||||
|
FROM subsystem
|
||||||
|
LEFT JOIN houses ON subsystem.room_id = houses.room_id
|
||||||
|
WHERE sub_space_name = ?
|
||||||
|
""", (sub_space_name,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
if res:
|
||||||
|
return {
|
||||||
|
"room_id": res[0][0],
|
||||||
|
"house_name": res[0][1],
|
||||||
|
"project_id": res[0][2],
|
||||||
|
"sub_system_id": res[0][3]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_aiot_platform_data(table_handler: BaseTable):
|
||||||
|
query = """
|
||||||
|
SELECT project_id, houses.room_id, house_name, COALESCE(subsystem_id, ''), COALESCE(sub_space_name, '')
|
||||||
|
FROM houses
|
||||||
|
LEFT JOIN subsystem ON subsystem.room_id = houses.room_id
|
||||||
|
"""
|
||||||
|
file_path = os.path.join(f"data/AIOT平台空间数据{now_datetime_second().replace(':', '_')}.xls")
|
||||||
|
sql_export_xls(query, table_handler.connection, file_path,
|
||||||
|
"房屋子系统映射数据表",
|
||||||
|
["项目id", "房屋id", "房屋名称", "子系统id", "子系统空间名称"])
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_sub_system_data(table_handler: BaseTable):
|
||||||
|
query = "SELECT subsystem_id, sub_space_name FROM subsystem"
|
||||||
|
file_path = os.path.join(f"data/子系统空间数据{now_datetime_second().replace(':', '_')}.xls")
|
||||||
|
sql_export_xls(query, table_handler.connection, file_path,
|
||||||
|
"子系统数据表",
|
||||||
|
["子系统id", "子系统空间名称"])
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update(table_handler: BaseTable, objs: list):
|
||||||
|
data = []
|
||||||
|
for obj in objs:
|
||||||
|
data.append((obj[0], now_datetime_second(), obj[1], obj[2]))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
UPDATE subsystem
|
||||||
|
SET room_id = ?, update_datetime = ?
|
||||||
|
WHERE subsystem_id = ? and sub_space_name = ?
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exits_room_id(table_handler: BaseTable, room_id: str):
|
||||||
|
table_handler.query("SELECT room_id FROM spaces WHERE room_id = ?", (room_id,))
|
||||||
|
if table_handler.cursor.fetchone() is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
200
SCLP/models/subsystem.py
Normal file
200
SCLP/models/subsystem.py
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 子系统房产信息表
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
from models.spaces import SpacesTable
|
||||||
|
from utils.database import BaseTable, get_table_handler
|
||||||
|
from utils.misc import now_datetime_second, InvalidException, generate_captcha_text
|
||||||
|
|
||||||
|
|
||||||
|
class HouseDetailInfo(BaseModel):
|
||||||
|
project_id: str
|
||||||
|
subsystem_id: str
|
||||||
|
subsystem_name: str
|
||||||
|
subsystem_data: str
|
||||||
|
createby_id: str
|
||||||
|
subsystem_extend: dict
|
||||||
|
create_time: Optional[int]
|
||||||
|
corp_id: Optional[str]
|
||||||
|
message_id: Optional[str] = Field(alias="_id")
|
||||||
|
action: str = "insert"
|
||||||
|
house_id: Optional[str] = None
|
||||||
|
house_name: Optional[str] = None
|
||||||
|
|
||||||
|
@field_validator("subsystem_extend")
|
||||||
|
def check_subsystem_extend(cls, value):
|
||||||
|
if value.get("label", None) is None:
|
||||||
|
raise InvalidException("subsystem_extend 中 label 值缺失")
|
||||||
|
return value
|
||||||
|
|
||||||
|
# @field_validator("house_id")
|
||||||
|
# def check_house_id(cls, value, values):
|
||||||
|
# th = get_table_handler()
|
||||||
|
# if value is None or value == "":
|
||||||
|
# label = values.data.get("subsystem_extend")["label"]
|
||||||
|
# space_info = SpacesTable.get_space_info_by_sub_space_name(th, label)
|
||||||
|
# if space_info is None:
|
||||||
|
# raise InvalidException(f"subsystem_extend - label:{label} 不在映射表中")
|
||||||
|
# if SubsystemTable.exits_room_id(th, space_info["room_id"]):
|
||||||
|
# raise InvalidException(f"house_id:{space_info['room_id']} 记录已存在,禁止插入操作")
|
||||||
|
# return space_info["room_id"]
|
||||||
|
# if not SpacesTable.exits_room_id(th, value):
|
||||||
|
# raise InvalidException("映射表中不存在对应的 house_id")
|
||||||
|
# else:
|
||||||
|
# return value
|
||||||
|
|
||||||
|
# @field_validator("house_name")
|
||||||
|
# def check_house_name(cls, value, values):
|
||||||
|
# th = get_table_handler()
|
||||||
|
# if value is None:
|
||||||
|
# label = values.data.get("subsystem_extend")["label"]
|
||||||
|
# space_info = SpacesTable.get_space_info_by_sub_space_name(th, label)
|
||||||
|
# if space_info is None:
|
||||||
|
# raise InvalidException(f"subsystem_extend - label:{label} 不在映射表中")
|
||||||
|
# if SubsystemTable.exits_room_id(th, space_info["room_id"]):
|
||||||
|
# raise InvalidException(f"house_id:{space_info['room_id']} 记录已存在,禁止插入操作")
|
||||||
|
# return space_info["house_name"]
|
||||||
|
# return value
|
||||||
|
|
||||||
|
|
||||||
|
class SubsystemTable(BaseTable):
|
||||||
|
@staticmethod
|
||||||
|
def check(table_handler: BaseTable):
|
||||||
|
"""检测是否存在当前表"""
|
||||||
|
table_handler.query("SELECT name FROM sqlite_master WHERE type='table' AND name='subsystem'")
|
||||||
|
if table_handler.cursor.fetchone() is None:
|
||||||
|
table_handler.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TABLE subsystem (
|
||||||
|
room_id TEXT UNIQUE,
|
||||||
|
subsystem_id TEXT,
|
||||||
|
subsystem_project_id TEXT,
|
||||||
|
sub_space_name TEXT,
|
||||||
|
subsystem_name TEXT,
|
||||||
|
subsystem_data TEXT,
|
||||||
|
subsystem_extend TEXT,
|
||||||
|
createby_id TEXT,
|
||||||
|
corp_id TEXT,
|
||||||
|
_id TEXT,
|
||||||
|
create_time INTEGER,
|
||||||
|
update_time INTEGER,
|
||||||
|
update_datetime TEXT,
|
||||||
|
PRIMARY KEY (subsystem_id, subsystem_project_id, sub_space_name)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
init_config_path = os.path.join(os.path.dirname(os.path.abspath("__file__")), "data/InitialData/subsystem.csv")
|
||||||
|
if os.path.exists(init_config_path):
|
||||||
|
with open(init_config_path, newline='', encoding='utf8') as csvfile:
|
||||||
|
csvreader = csv.reader(csvfile)
|
||||||
|
head = next(csvreader)
|
||||||
|
if len(head) == 10:
|
||||||
|
data = []
|
||||||
|
for row in csvreader:
|
||||||
|
room_id = row[0].strip()
|
||||||
|
subsystem_project_id = row[1].strip()
|
||||||
|
sub_space_name = row[2].strip()
|
||||||
|
subsystem_name = row[3].strip()
|
||||||
|
subsystem_data = row[4].strip()
|
||||||
|
subsystem_extend = row[5].strip()
|
||||||
|
createby_id = row[6].strip()
|
||||||
|
corp_id = row[7].strip()
|
||||||
|
_id = row[8].strip()
|
||||||
|
create_time = int(row[9].strip())
|
||||||
|
update_datetime = now_datetime_second()
|
||||||
|
data.append((room_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
|
||||||
|
createby_id, corp_id, _id, create_time, int(time.time() * 1000), update_datetime))
|
||||||
|
table_handler.executemany(
|
||||||
|
f"""
|
||||||
|
INSERT INTO subsystem
|
||||||
|
(room_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
|
||||||
|
createby_id, corp_id, _id, create_time, update_time, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT (room_id) DO NOTHING
|
||||||
|
""", data
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_house_detail_info_by_project_id(table_handler: BaseTable, project_id: str) -> list:
|
||||||
|
"""根据项目ID获取查找子系统空间结构中的房间信息"""
|
||||||
|
table_handler.query(
|
||||||
|
"""
|
||||||
|
SELECT subsystem.room_id, sub_space_name, house_name, subsystem_project_id,
|
||||||
|
subsystem_id, subsystem_name, subsystem_data, subsystem_extend,
|
||||||
|
createby_id, corp_id, _id, create_time, update_time
|
||||||
|
FROM subsystem
|
||||||
|
LEFT JOIN houses ON subsystem.room_id = houses.room_id
|
||||||
|
WHERE subsystem_project_id = ?
|
||||||
|
ORDER BY create_time DESC
|
||||||
|
""", (project_id,)
|
||||||
|
)
|
||||||
|
res = table_handler.cursor.fetchall()
|
||||||
|
house_detail_info_list = []
|
||||||
|
if res:
|
||||||
|
for info in res:
|
||||||
|
subsystem_extend = json.loads(info[7].replace("'", '"').replace("None", "null"))
|
||||||
|
subsystem_extend["label"] = info[1]
|
||||||
|
house_detail_info_list.append({
|
||||||
|
"house_id": info[0] if info[0] else "",
|
||||||
|
"house_name": info[2] if info[0] else "",
|
||||||
|
"project_id": info[3],
|
||||||
|
"subsystem_id": info[4],
|
||||||
|
"subsystem_name": info[5],
|
||||||
|
"subsystem_data": info[6],
|
||||||
|
"subsystem_extend": subsystem_extend,
|
||||||
|
"createby_id": info[8],
|
||||||
|
"corp_id": info[9],
|
||||||
|
"_id": generate_captcha_text(16).lower(),
|
||||||
|
"create_time": info[11],
|
||||||
|
"update_time": info[12],
|
||||||
|
"is_stop": False,
|
||||||
|
"valid": True,
|
||||||
|
"ids": None
|
||||||
|
})
|
||||||
|
return house_detail_info_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_sub_system_house_info(table_handler: BaseTable, obj: HouseDetailInfo):
|
||||||
|
"""添加房产信息"""
|
||||||
|
nt = now_datetime_second()
|
||||||
|
if obj.action == "insert":
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
INSERT OR IGNORE INTO subsystem
|
||||||
|
(subsystem_id, subsystem_project_id, sub_space_name, subsystem_name, subsystem_data, subsystem_extend,
|
||||||
|
createby_id, corp_id, _id, create_time, update_time, update_datetime)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (obj.subsystem_id, obj.project_id, obj.subsystem_extend["label"], obj.subsystem_name, obj.subsystem_data, str(obj.subsystem_extend),
|
||||||
|
obj.createby_id, obj.corp_id, obj.message_id, obj.create_time, int(time.time() * 1000), nt)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
table_handler.execute(
|
||||||
|
"""
|
||||||
|
UPDATE subsystem
|
||||||
|
SET subsystem_id = ?, subsystem_project_id = ?, sub_space_name = ?, subsystem_name = ?,
|
||||||
|
subsystem_data = ?, subsystem_extend = ?,
|
||||||
|
createby_id = ?, corp_id = ?, _id = ?, create_time = ?, update_time = ?, update_datetime = ?
|
||||||
|
WHERE room_id = ?
|
||||||
|
""", (obj.subsystem_id, obj.project_id, obj.subsystem_extend["label"], obj.subsystem_name,
|
||||||
|
obj.subsystem_data, str(obj.subsystem_extend),
|
||||||
|
obj.createby_id, obj.corp_id, obj.message_id,
|
||||||
|
obj.create_time, int(time.time() * 1000), nt, obj.house_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exits_room_id(table_handler: BaseTable, room_id: str):
|
||||||
|
table_handler.query("SELECT room_id FROM subsystem WHERE room_id = ?", (room_id,))
|
||||||
|
if table_handler.cursor.fetchone() is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
34
SCLP/readme.md
Normal file
34
SCLP/readme.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# 项目简介
|
||||||
|
|
||||||
|
> 智慧社区本地化平台 Smart Community Localization Platform,基于python实现的平台后端
|
||||||
|
|
||||||
|
# 功能模块
|
||||||
|
|
||||||
|
* 内置信息导入
|
||||||
|
* 设备管理
|
||||||
|
* 住户管理
|
||||||
|
* 通行管理
|
||||||
|
* 智慧停车
|
||||||
|
|
||||||
|
# 数据表
|
||||||
|
|
||||||
|
* **brands** 可视对讲厂商信息表
|
||||||
|
- 初始化后只会进行更新不会进行增删
|
||||||
|
|
||||||
|
* **devices** 设备信息表
|
||||||
|
- 基本aiot设备信息,接受边缘端注册、上线过程中保存的信息
|
||||||
|
|
||||||
|
* **devices_auth** 设备授权信息表
|
||||||
|
- 记录设备授权过程中的信息
|
||||||
|
|
||||||
|
* **devices_scope** 单元ID与实际设备关系表
|
||||||
|
- 在设备配置关联信息时插入,记录每一个单元权限下的所有设备
|
||||||
|
- 移除设备关联信息时,在完成设备端授权清除后会清除该表中的关系
|
||||||
|
|
||||||
|
* **parkinglots** 停车场信息表
|
||||||
|
- 在一张表中记录停车场、区域、通道、道闸的信息,通过type字段进行区分
|
||||||
|
- 停车场:id(id就是编号)\number(aiot设备ID)\name\factory_name\running_status(是否为null控制页面显示)
|
||||||
|
- 区域:id\number\name\parkinglot_id\area_id(如果存在父区域的话为父区域ID)
|
||||||
|
- 通道:id\number\name\parkinglot_id\area_id\channel_type
|
||||||
|
- 道闸:id\number\name\parkinglot_id\area_id\channel_id\is_online\gate_status\running_status(工作状态)
|
||||||
|
|
||||||
77
SCLP/routers/access_devices_0.py
Normal file
77
SCLP/routers/access_devices_0.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 通行设备界面控制逻辑
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Request, Header, Query
|
||||||
|
|
||||||
|
from models.devices import DevicesTable, AccessDevicesScope, BuildingDevicesScope
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getAccessDevicesInfo", summary="获取通行设备信息")
|
||||||
|
async def get_access_devices_info(request: Request,
|
||||||
|
is_associated: bool = Query(...),
|
||||||
|
device_name: str = Query(None),
|
||||||
|
product_name: str = Query(None),
|
||||||
|
device_mac: str = Query(None),
|
||||||
|
device_id: int = Query(None),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""获取通行设备信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
if device_name:
|
||||||
|
resp = DevicesTable.get_access_devices_info(th, is_associated, device_name=device_name)
|
||||||
|
elif product_name:
|
||||||
|
resp = DevicesTable.get_access_devices_info(th, is_associated, product_name=product_name)
|
||||||
|
elif device_mac:
|
||||||
|
resp = DevicesTable.get_access_devices_info(th, is_associated, device_mac=device_mac)
|
||||||
|
elif device_id:
|
||||||
|
resp = DevicesTable.get_access_devices_info(th, is_associated, device_id=device_id)
|
||||||
|
else:
|
||||||
|
resp = DevicesTable.get_access_devices_info(th, is_associated)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addAccessDevicesAssociateInfo", summary="批量增加大门门禁/大门闸机关联信息")
|
||||||
|
async def add_access_devices_associate_info(request: Request, item: AccessDevicesScope, token: str = Header(...)):
|
||||||
|
"""批量增加大门门禁/大门闸机关联信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
_callback = DevicesTable.add_access_devices(th, item.device_type, item.info)
|
||||||
|
resp = {"status": _callback}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addBuildingDevicesAssociateInfo", summary="批量增加楼栋门禁关联信息")
|
||||||
|
async def add_building_devices_associate_info(request: Request, item: BuildingDevicesScope, token: str = Header(...)):
|
||||||
|
"""批量增加楼栋门禁关联信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
_callback = DevicesTable.add_building_devices(th, "楼栋门禁", item.info)
|
||||||
|
resp = {"status": _callback}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/deleteAssociatedAccessDeviceInfo", summary="移除完成关联的设备及其已授权的人员信息")
|
||||||
|
async def delete_access_device_info(request: Request, device_id: int = Query(alias="id"), token: str = Header(...)):
|
||||||
|
"""移除完成关联的设备及其已授权的人员信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = DevicesTable.delete_access_device_info(th, device_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
193
SCLP/routers/access_devices_1.py
Normal file
193
SCLP/routers/access_devices_1.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 通行授权界面控制逻辑
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request, Header, File, UploadFile, Query
|
||||||
|
|
||||||
|
from config import PREFIX_PATH, SUB_PATH
|
||||||
|
from config import APP_HOST as HOST_IP
|
||||||
|
from config import IMAGE_SERVER_PORT as PORT
|
||||||
|
from models.devices import SearchDevicesInfo, DevicesTable
|
||||||
|
from models.householders import HouseholdersTable, GetAuthHouseholdersInfo, GetAuthUsersInfo, \
|
||||||
|
UpdateHouseholderFace, AddAuthUserInfo, UpdateAuthUserInfo
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getHouseholdersInfo", summary="授权住户查询")
|
||||||
|
async def get_householders_info(request: Request,
|
||||||
|
page: int = Query(..., gt=0),
|
||||||
|
limit: int = Query(..., gt=0),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""获取已授权的住户信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_auth_householder_info(th, None, None, None, None,
|
||||||
|
page, limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/getHouseholdersInfo", summary="授权住户条件查询")
|
||||||
|
async def get_householders_info_by_condition(request: Request,
|
||||||
|
item: GetAuthHouseholdersInfo,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""根据条件获取已授权的住户信息查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item.__dict__}")
|
||||||
|
resp = HouseholdersTable.get_auth_householder_info(th, item.search_type, item.search_key,
|
||||||
|
item.space_type, item.space_id,
|
||||||
|
item.page, item.limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getHouseholderDetailInfo", summary="住户授权详细信息查询")
|
||||||
|
async def get_householder_detail_info(request: Request, householder_id: int = Query(...), token: str = Header(...)):
|
||||||
|
"""获取指定住户授权详细信息查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_auth_householder_detail_info(th, householder_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getUsersInfo", summary="通用授权人员信息查询")
|
||||||
|
async def get_users_info(request: Request,
|
||||||
|
page: int = Query(..., gt=0),
|
||||||
|
limit: int = Query(..., gt=0),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""获取已授权的通用人员信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_auth_users_info(th, None, None, None, page, limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/getUsersInfo", summary="通用授权人员信息条件查询")
|
||||||
|
async def get_users_info_by_condition(request: Request,
|
||||||
|
item: GetAuthUsersInfo,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""根据条件获取已授权的通用人员信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_auth_users_info(th, item.search_type, item.search_key,
|
||||||
|
item.user_type, item.page, item.limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getUserDetailInfo", summary="通用授权人员详细信息查询")
|
||||||
|
async def get_user_detail_info(request: Request,
|
||||||
|
user_id: int = Query(alias="id"),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""获取指定通用人员的详细信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_auth_user_info(th, user_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/deleteUserInfo", summary="删除通用授权人员信息及关联设备授权")
|
||||||
|
async def get_users_info(request: Request, user_id: int = Query(...), token: str = Header(...)):
|
||||||
|
"""通用授权人员删除 & 移除关联设备授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.delete_auth_user_info(th, user_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addUserInfo", summary="新增通用授权人员信息及关联设备授权")
|
||||||
|
async def add_user_info(request: Request, item: AddAuthUserInfo, token: str = Header(...)):
|
||||||
|
"""新增通用授权人员 & 人脸图像绑定 & 添加关联设备授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.add_auth_user_info(th, item)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/updateUserInfo", summary="更新通用授权人员信息及关联设备授权")
|
||||||
|
async def update_user_info(request: Request, item: UpdateAuthUserInfo, token: str = Header(...)):
|
||||||
|
"""更新通用授权人员信息 & 人脸图像绑定 & 判断是否需要刷新关联设备授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.update_auth_user_info(th, item)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/uploadHouseholderFace", summary="人脸图片上传")
|
||||||
|
async def upload_householder_face(request: Request,
|
||||||
|
face_file: UploadFile = File(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""接收人脸图片上传并保存"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
# 检查文件类型是否为图像
|
||||||
|
if not face_file.content_type.startswith("image"):
|
||||||
|
resp = {"status": False, "message": "仅支持上传图片"}
|
||||||
|
else:
|
||||||
|
file_content = await face_file.read()
|
||||||
|
max_size_mb = 10 # 设置最大允许的文件大小为 10MB
|
||||||
|
if face_file.file.seek(0, os.SEEK_END) > max_size_mb * 1024 * 1024:
|
||||||
|
resp = {"status": False, "message": f"文件大小不得超过 {max_size_mb}MB"}
|
||||||
|
else:
|
||||||
|
save_path = f"./data/FaceImages"
|
||||||
|
filename = f"_{int(time.time() * 1000)}"
|
||||||
|
if not os.path.exists(save_path):
|
||||||
|
os.mkdir(save_path)
|
||||||
|
file_path = f"{save_path}/{filename}.jpg"
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(file_content)
|
||||||
|
|
||||||
|
# 返回成功消息及人脸照片的 URL
|
||||||
|
face_url = f"http://{HOST_IP}:{PORT}{PREFIX_PATH}/{SUB_PATH}/{filename}.jpg"
|
||||||
|
resp = {"face_url": face_url}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/updateHouseholderFace", summary="更新住户人脸信息")
|
||||||
|
async def get_users_info(request: Request, item: UpdateHouseholderFace, token: str = Header(...)):
|
||||||
|
"""人脸图像与住户ID绑定 & 刷新关联设备授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.update_householder_face(th, item.id, item.face_url)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/getAssociatedAccessDevicesInfo", summary="通用授权-门禁设备条件查询")
|
||||||
|
async def get_associated_access_devices_info(request: Request, item: SearchDevicesInfo, token: str = Header(...)):
|
||||||
|
"""已具备关联空间信息的门禁设备条件查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = DevicesTable.get_associated_access_devices_info(th, item.search_type, item.search_key)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
30
SCLP/routers/devices_manage.py
Normal file
30
SCLP/routers/devices_manage.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Created :
|
||||||
|
@Updated :
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 设备管理界面控制逻辑
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Request, Header
|
||||||
|
|
||||||
|
from models.devices import DevicesTable
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getAllDevicesInfo", summary="获取全量的设备信息")
|
||||||
|
async def get_all_devices_info(request: Request, token: str = Header(...)):
|
||||||
|
"""获取全量的设备信息列表"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
devices_info = DevicesTable.get_devices_info(th)
|
||||||
|
resp = {"devices": devices_info}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
163
SCLP/routers/edge_simulation_api.py
Normal file
163
SCLP/routers/edge_simulation_api.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 模拟边缘对接所需的平台api接口
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Request, Query
|
||||||
|
|
||||||
|
from models.householders import HouseholdersTable
|
||||||
|
from models.houses import HousesTable
|
||||||
|
from models.spaces import SpacesTable
|
||||||
|
from models.subsystem import SubsystemTable, HouseDetailInfo
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException, snake2camel_list_dict
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2/accesskey_auth", summary="回传一个无效的随机字符串")
|
||||||
|
async def access_key_auth(_item: dict):
|
||||||
|
return {"access_token": "Q0E5QjU4QUNDMDA4MTY4RDNFNkRGRjNGNzQ4NDAzMTQ5OTVBMDQwODMyNjBFMjBCQkIwQjA2QzlCNUQ3MUY3NA=="}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/v3/realty-master-data/owners/{user_id}", summary="根据用户 id 获取房产信息")
|
||||||
|
async def get_property_info_by_user_id(request: Request, user_id: int):
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {user_id}")
|
||||||
|
th = get_table_handler()
|
||||||
|
house_ids = HouseholdersTable.get_room_ids(th, user_id)
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"house_ids": house_ids
|
||||||
|
},
|
||||||
|
"code": 200,
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v3/realty-master-data/houses", summary="根据房产 id 获取房产详细信息")
|
||||||
|
async def get_house_detail_info(request: Request, item: dict):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item}")
|
||||||
|
house_ids = item["query"]["id"]["$in"]
|
||||||
|
house_detail_list = []
|
||||||
|
th = get_table_handler()
|
||||||
|
for house_id in house_ids:
|
||||||
|
house_detail_info = HousesTable.get_house_detail_info(th, house_id)
|
||||||
|
if house_detail_info is not None:
|
||||||
|
house_detail_list.append(house_detail_info)
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"total_count": len(house_detail_list),
|
||||||
|
"total_page": 0,
|
||||||
|
"list": house_detail_list
|
||||||
|
},
|
||||||
|
"code": 200,
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise InvalidException("请求参数结构异常")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2/realty-master-data/SubsystemSpaceMap/list", summary="查找空间结构中的房间信息")
|
||||||
|
async def get_subsystem_house_detail_info(request: Request, item: dict):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item}")
|
||||||
|
project_id = item["query"]["project_id"]["$eq"]
|
||||||
|
th = get_table_handler()
|
||||||
|
data_list = SubsystemTable.get_house_detail_info_by_project_id(th, project_id)
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"total_count": len(data_list),
|
||||||
|
"total_page": 0,
|
||||||
|
"list": data_list
|
||||||
|
},
|
||||||
|
"code": 200,
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} -> {resp}")
|
||||||
|
return resp
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise InvalidException("请求参数结构异常")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2/realty-master-data/SubsystemSpaceMap/added", summary="添加房产信息")
|
||||||
|
async def add_subsystem_house_info(request: Request, item: HouseDetailInfo):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item}")
|
||||||
|
th = get_table_handler()
|
||||||
|
SubsystemTable.add_sub_system_house_info(th, item)
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": item.message_id,
|
||||||
|
"code": 200,
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise InvalidException("请求参数结构异常")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2/realty-master-data/SubsystemSpaceMap/update", summary="更新房产信息")
|
||||||
|
async def update_subsystem_house_info(request: Request, item: HouseDetailInfo):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item}")
|
||||||
|
th = get_table_handler()
|
||||||
|
item.action = "update"
|
||||||
|
SubsystemTable.add_sub_system_house_info(th, item)
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": item.message_id,
|
||||||
|
"code": 200,
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise InvalidException("请求参数结构异常")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2/smart-access/house/house_ty_maps", summary="查询房产的真实信息")
|
||||||
|
async def get_house_ty_info(request: Request, item: dict):
|
||||||
|
try:
|
||||||
|
logger.Logger.debug(f"{request.url.path} <- {item}")
|
||||||
|
project_id = item["query"]["project_id"]["$eq"]
|
||||||
|
room_id = item["query"]["_id"]["$eq"]
|
||||||
|
th = get_table_handler()
|
||||||
|
house_ty_info = HousesTable.get_house_ty_info(th, project_id, room_id)
|
||||||
|
if house_ty_info is None:
|
||||||
|
house_ty_infos = []
|
||||||
|
else:
|
||||||
|
house_ty_infos = [house_ty_info]
|
||||||
|
resp = {
|
||||||
|
"status": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"data": {
|
||||||
|
"count": 1,
|
||||||
|
"list": house_ty_infos,
|
||||||
|
"query_res": {
|
||||||
|
"count": 1,
|
||||||
|
"list": snake2camel_list_dict(house_ty_infos)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"det": 0
|
||||||
|
}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
raise InvalidException("请求参数结构异常")
|
||||||
84
SCLP/routers/householder_manage.py
Normal file
84
SCLP/routers/householder_manage.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 住户管理界面控制逻辑
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Request, Header, Query
|
||||||
|
|
||||||
|
from models.householders import HouseholdersTable, GetHouseholdersInfo, AddHouseholderInfo, UpdateHouseholderInfo
|
||||||
|
from models.houses import HousesTable
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addHouseholderInfo", summary="新增住户信息")
|
||||||
|
async def add_householder_info(request: Request, item: AddHouseholderInfo, token: str = Header(...)):
|
||||||
|
"""新增住户信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
# 进行住户信息插入
|
||||||
|
householder_id = HouseholdersTable.insert(th, item.name, item.sex, item.phone, "住户")
|
||||||
|
if householder_id:
|
||||||
|
for room_id, _type in item.property_info:
|
||||||
|
# 房产关联表信息插入
|
||||||
|
HouseholdersTable.insert_type(th, room_id, householder_id, _type)
|
||||||
|
# 更新房产中的住户数量
|
||||||
|
old_count = HousesTable.get_householder_count(th, room_id)
|
||||||
|
HousesTable.update_householder_count(th, room_id, old_count + 1)
|
||||||
|
resp = {"status": True}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/getHouseholderInfo", summary="获取住户信息")
|
||||||
|
async def get_householder_info(request: Request, item: GetHouseholdersInfo, token: str = Header(...)):
|
||||||
|
"""获取住户信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_householder_info(th, item.search_type, item.search_key, item.role_type,
|
||||||
|
item.page, item.limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getHouseholderInfo", summary="查询指定住户信息")
|
||||||
|
async def get_householder_detail_info(request: Request,
|
||||||
|
householder_id: int = Query(alias="id"),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""查询指定住户信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.get_householder_info_by_id(th, householder_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/updateHouseholderInfo", summary="更新指定住户信息")
|
||||||
|
async def update_householder_info(request: Request, item: UpdateHouseholderInfo, token: str = Header(...)):
|
||||||
|
"""更新指定住户信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.update_householder_info(th, item.householder_id, item.property_info)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/deleteHouseholderInfo", summary="删除指定住户信息及移除其关联设备授权")
|
||||||
|
async def delete_householder_info(request: Request, householder_id: int = Query(alias="id"), token: str = Header(...)):
|
||||||
|
"""删除指定住户的信息及其关联授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = HouseholdersTable.delete_householder_info(th, householder_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
86
SCLP/routers/houses_manage.py
Normal file
86
SCLP/routers/houses_manage.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 房产信息控制逻辑
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Header, Request, Query
|
||||||
|
from pydantic import BaseModel, field_validator, Field
|
||||||
|
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from models.houses import HousesTable
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class ListsReq(BaseModel):
|
||||||
|
project: str = Field(description="必填项,用于限定项目编号")
|
||||||
|
area: Optional[str] = Field(None, description="可选项,用于限定区域")
|
||||||
|
building: Optional[str] = Field(None, description="可选项,用于限定楼栋")
|
||||||
|
unit: Optional[str] = Field(None, description="可选项,用于限定单元")
|
||||||
|
floor: Optional[str] = Field(None, description="可选项,用于限定楼层")
|
||||||
|
target: str = Field(description="必填项,用于控制输出结果,取值范围:area, building, unit, floor, room")
|
||||||
|
|
||||||
|
def dict(self, *args, **kwargs):
|
||||||
|
_dict = self.__dict__.copy()
|
||||||
|
for key, value in self.__dict__.items():
|
||||||
|
if value is None:
|
||||||
|
_dict.pop(key)
|
||||||
|
_dict.pop("target")
|
||||||
|
return _dict
|
||||||
|
|
||||||
|
@field_validator("target")
|
||||||
|
def validate_target(cls, value):
|
||||||
|
if value not in ["area", "building", "unit", "floor", "room"]:
|
||||||
|
raise InvalidException("请提供正确的请求target [area, building, unit, floor, room]")
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/projects", summary="获取所有项目列表")
|
||||||
|
async def get_projects_list(request: Request, token: str = Header(...)):
|
||||||
|
"""获取项目列表"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
projects_list = HousesTable.get_items_by_dynamic_item(th, "project", {})
|
||||||
|
resp = {"projects": projects_list}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/project/lists", summary="动态获取房产信息")
|
||||||
|
async def get_items(request: Request,
|
||||||
|
item: ListsReq,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""根据请求获取列表"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
item_list = HousesTable.get_items_by_dynamic_item(th, item.target, item.dict())
|
||||||
|
resp = {f"{item.target.split('_')[0]}s": item_list}
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getHouseholderCount", summary="获取房产住户数量")
|
||||||
|
async def get_householder_count(request: Request,
|
||||||
|
room_id: str = Query(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""根据请求获取对应房屋中当前住户数量"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
count = HousesTable.get_householder_count(th, room_id)
|
||||||
|
if count is not None:
|
||||||
|
resp = {"count": count}
|
||||||
|
else:
|
||||||
|
raise InvalidException("room_id 不存在")
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
140
SCLP/routers/login.py
Normal file
140
SCLP/routers/login.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 登陆界面控制逻辑
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import os.path
|
||||||
|
import random
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
from starlette.responses import StreamingResponse
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastapi import APIRouter, Query, Request
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils import logger
|
||||||
|
from utils.misc import generate_captcha_text
|
||||||
|
from models.sessions import SessionsTable
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class LoginItem(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
session_id: str
|
||||||
|
captcha: str
|
||||||
|
|
||||||
|
|
||||||
|
class LoginResp(BaseModel):
|
||||||
|
status: bool
|
||||||
|
message: str
|
||||||
|
token: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_captcha_image(text, size=(120, 40), font_path='./data/mvboli.ttf', font_size=24):
|
||||||
|
"""生成验证码图片"""
|
||||||
|
image = Image.new('RGB', size, (73, 109, 137)) # 设置背景色
|
||||||
|
d = ImageDraw.Draw(image)
|
||||||
|
assert os.path.exists(font_path), "字体文件不存在"
|
||||||
|
font = ImageFont.truetype(font_path, font_size)
|
||||||
|
# d.text((5, 5), text, font=font, fill=(255, 255, 0)) # 设置字体颜色和位置
|
||||||
|
font_box = font.getbbox(text)
|
||||||
|
text_width, text_height = font_box[2] - font_box[0], font_box[3] - font_box[1]
|
||||||
|
text_x = (size[0] - text_width) // 2
|
||||||
|
text_y = (size[1] - text_height) // 2
|
||||||
|
d.text((text_x, text_y - 8), text, font=font, fill=(255, 255, 0))
|
||||||
|
|
||||||
|
# 添加噪点
|
||||||
|
for _ in range(100): # 可以根据需要调整噪点数量
|
||||||
|
x = random.randint(0, size[0] - 1)
|
||||||
|
y = random.randint(0, size[1] - 1)
|
||||||
|
image.putpixel((x, y), (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
|
||||||
|
|
||||||
|
# 添加线条
|
||||||
|
for _ in range(3): # 可以根据需要调整线条数量
|
||||||
|
x1, y1 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1)
|
||||||
|
x2, y2 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1)
|
||||||
|
d.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
|
||||||
|
width=2)
|
||||||
|
|
||||||
|
# 将图片保存为字节流
|
||||||
|
bytes_io = io.BytesIO()
|
||||||
|
image.save(bytes_io, format='PNG')
|
||||||
|
# image.save("./data/tmp.png", format='PNG')
|
||||||
|
bytes_io.seek(0)
|
||||||
|
return bytes_io
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
"""对密码进行 SHA-256 哈希加密"""
|
||||||
|
password_bytes = password.encode('utf-8')
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
sha256_hash.update(password_bytes)
|
||||||
|
return sha256_hash.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(password, hashed_password):
|
||||||
|
"""比对密码和哈希值"""
|
||||||
|
password_hash = hash_password(password)
|
||||||
|
return password_hash.lower() == hashed_password.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate_token(token: str):
|
||||||
|
th = get_table_handler()
|
||||||
|
timeout_timestamp = str(int(time.time() * 1000 - 4 * 60 * 60 * 1000))
|
||||||
|
return SessionsTable.check_token(th, timeout_timestamp, token)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/generateCaptcha", summary="生成验证码")
|
||||||
|
async def generate_captcha_endpoint(request: Request,
|
||||||
|
session_id: str = Query(None, description="Session ID")):
|
||||||
|
"""生成验证码图片并返回"""
|
||||||
|
if session_id:
|
||||||
|
captcha_text = generate_captcha_text()
|
||||||
|
logger.Logger.debug(f"{request.url.path} 验证码生成:{captcha_text}")
|
||||||
|
# session_id 入库
|
||||||
|
th = get_table_handler()
|
||||||
|
SessionsTable.insert(th, session_id, captcha_text)
|
||||||
|
captcha_image = generate_captcha_image(captcha_text)
|
||||||
|
# 设置HTTP响应的头部,指定内容类型为PNG图片
|
||||||
|
headers = {"Content-Type": "image/png"}
|
||||||
|
return StreamingResponse(captcha_image, headers=headers)
|
||||||
|
else:
|
||||||
|
logger.Logger.error(f"{request.url.path} 请求参数缺少 session_id - {request.client.host}")
|
||||||
|
return {"status": False, "message": "请求参数缺少 session_id"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login", response_model=LoginResp, response_model_exclude_none=True, summary="登录请求")
|
||||||
|
async def login(item: LoginItem):
|
||||||
|
"""简单的账户登录逻辑"""
|
||||||
|
status = False
|
||||||
|
token = None
|
||||||
|
th = get_table_handler()
|
||||||
|
# 验证码校验
|
||||||
|
right_captcha = SessionsTable.get_captcha(th, item.session_id)
|
||||||
|
if right_captcha is not None:
|
||||||
|
if item.captcha.lower() == right_captcha.lower():
|
||||||
|
# 账户密码校验
|
||||||
|
if item.username == "admin":
|
||||||
|
if verify_password(item.password, "B12AD23C7E230E2CA365508DD16635DD3D7214BCD9BEA27457A356FD5C15F8BF"):
|
||||||
|
status = True
|
||||||
|
message = "校验通过"
|
||||||
|
token = generate_captcha_text(16)
|
||||||
|
SessionsTable.update(th, item.session_id, item.username, token)
|
||||||
|
else:
|
||||||
|
message = "密码错误"
|
||||||
|
else:
|
||||||
|
message = "账户不存在"
|
||||||
|
else:
|
||||||
|
message = "验证码错误"
|
||||||
|
else:
|
||||||
|
message = "无效 session_id"
|
||||||
|
resp = LoginResp(status=status, message=message, token=token)
|
||||||
|
logger.Logger.debug("/login " + str(resp.__dict__))
|
||||||
|
return resp
|
||||||
85
SCLP/routers/parkinglot_0.py
Normal file
85
SCLP/routers/parkinglot_0.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 车场管理界面控制逻辑
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Request, Header, Query
|
||||||
|
|
||||||
|
from models.parkinglots import ParkinglotsTable, AddParkingLot
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getParkingLotsInfo", summary="车场信息查询")
|
||||||
|
async def get_parkinglots_info(request: Request, token: str = Header(...)):
|
||||||
|
"""获取车场信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_parkinglots_info(th)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addParkingLot", summary="添加车场")
|
||||||
|
async def add_parkinglot(request: Request, item: AddParkingLot, token: str = Header(...)):
|
||||||
|
"""添加显示信息的车场 & 更新车场编号对应的名字和供应商"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
factory_name = item.factory_name if item.factory_name else ''
|
||||||
|
resp = ParkinglotsTable.update_parkinglot_show(th, item.parkinglot_number, item.parkinglot_name, factory_name, True)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/deleteParkingLot", summary="移除车场")
|
||||||
|
async def delete_parkinglot(request: Request,
|
||||||
|
parkinglot_number: str = Query(alias="number", description="车场编号"),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""移除显示信息的车场"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.update_parkinglot_show(th, parkinglot_number, None, None, False)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getParkingLotsAreaInfo", summary="区域信息查询")
|
||||||
|
async def get_parkinglots_area_info(request: Request, token: str = Header(...)):
|
||||||
|
"""区域信息查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_showed_parkinglot_area_info(th)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getParkingLotsChannelInfo", summary="通道信息查询")
|
||||||
|
async def get_parkinglots_channel_info(request: Request, token: str = Header(...)):
|
||||||
|
"""通道信息查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_showed_parkinglot_channel_info(th)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getParkingLotsGateInfo", summary="道闸信息查询")
|
||||||
|
async def get_parkinglots_channel_info(request: Request, token: str = Header(...)):
|
||||||
|
"""道闸信息查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_showed_parkinglot_gate_info(th)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
108
SCLP/routers/parkinglot_1.py
Normal file
108
SCLP/routers/parkinglot_1.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 车辆授权界面控制逻辑
|
||||||
|
"""
|
||||||
|
from fastapi import APIRouter, Query, Request, Header
|
||||||
|
|
||||||
|
from models.parkinglots import ParkinglotsTable, GetCarsInfo, AddCarsCard, UpdateCarsCard
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getCarsInfo", summary="授权车辆信息查询")
|
||||||
|
async def get_cars_info(request: Request,
|
||||||
|
page: int = Query(None),
|
||||||
|
limit: int = Query(None),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""授权车辆信息全量查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_cars_info(th, None, None, page, limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/getCarsInfo", summary="授权车辆信息条件查询")
|
||||||
|
async def get_cars_info(request: Request,
|
||||||
|
item: GetCarsInfo,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""授权车辆信息全量查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_cars_info(th, item.search_type, item.search_key, item.page, item.limit)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getCarDetailInfo", summary="详细授权信息查询")
|
||||||
|
async def get_car_detail_info(request: Request,
|
||||||
|
auth_id: int = Query(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""授权车辆信息全量查询"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_auth_info(th, auth_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getCarIdAuthStatus", summary="获取车牌号授权状态")
|
||||||
|
async def get_car_id_auth_status(request: Request,
|
||||||
|
car_id: str = Query(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""获取车牌号授权状态"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.get_car_id_auth_status(th, car_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/deleteCarAuthId", summary="删除车辆授权")
|
||||||
|
async def delete_car_auth_id(request: Request,
|
||||||
|
auth_id: int = Query(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""删除车辆月卡授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.delete_car_auth_id(th, auth_id)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/addCarAuthInfo", summary="新增车辆授权")
|
||||||
|
async def add_car_card_auth(request: Request,
|
||||||
|
item: AddCarsCard,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""新增车辆月卡授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.add_car_card_auth(th, item)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/updateCarInfo", summary="更新车辆授权")
|
||||||
|
async def add_car_card_auth(request: Request,
|
||||||
|
item: UpdateCarsCard,
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""更新车辆月卡授权"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = ParkinglotsTable.update_car_card_auth(th, item)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
101
SCLP/routers/space_manage.py
Normal file
101
SCLP/routers/space_manage.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 通行空间映射界面控制逻辑
|
||||||
|
"""
|
||||||
|
import os.path
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request, Header, BackgroundTasks, UploadFile, File
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
from models.brands import BrandsTable, UpdateBrand
|
||||||
|
from models.spaces import SpacesTable
|
||||||
|
from routers.login import authenticate_token
|
||||||
|
from utils import logger
|
||||||
|
from utils.logger import TOKEN_ERROR
|
||||||
|
from utils.database import get_table_handler
|
||||||
|
from utils.misc import InvalidException, valid_xls
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/updateSubSystemPlatformName", summary="更新当前使用的可视对讲厂家")
|
||||||
|
async def update_sub_system_platform_name(request: Request, item: UpdateBrand, token: str = Header(...)):
|
||||||
|
"""更新当前使用的可视对讲厂家"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = BrandsTable.update_checked_factory(th, item.name)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/getSubSystemPlatformName", summary="查询当前使用的可视对讲厂家")
|
||||||
|
async def get_sub_system_platform_name(request: Request, token: str = Header(...)):
|
||||||
|
"""获取当前启用的可视对讲厂家"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
resp = BrandsTable.get_checked_factory(th)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/exportAiotPlatformData", summary="导出AIOT平台空间数据")
|
||||||
|
async def export_aiot_platform_data(request: Request, background_tasks: BackgroundTasks, token: str = Header(...)):
|
||||||
|
"""导出AIOT平台空间数据"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
file_path = SpacesTable.get_aiot_platform_data(th)
|
||||||
|
background_tasks.add_task(os.remove, file_path)
|
||||||
|
logger.Logger.debug(f"{request.url.path} 完成AIOT平台空间数据导出")
|
||||||
|
return FileResponse(path=file_path,
|
||||||
|
filename=os.path.basename(file_path),
|
||||||
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/exportSubSystemData", summary="导出子系统空间结构数据表")
|
||||||
|
async def export_aiot_platform_data(request: Request, background_tasks: BackgroundTasks, token: str = Header(...)):
|
||||||
|
"""导出子系统空间结构数据表"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
th = get_table_handler()
|
||||||
|
file_path = SpacesTable.get_sub_system_data(th)
|
||||||
|
background_tasks.add_task(os.remove, file_path)
|
||||||
|
logger.Logger.debug(f"{request.url.path} 完成子系统空间数据导出")
|
||||||
|
return FileResponse(path=file_path,
|
||||||
|
filename=os.path.basename(file_path),
|
||||||
|
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/importIdMapData", summary="导入空间映射表")
|
||||||
|
async def import_id_map_data(request: Request,
|
||||||
|
xls_file: UploadFile = File(...),
|
||||||
|
token: str = Header(...)):
|
||||||
|
"""接收空间映射表xls表格上传、校验并更新对应映射信息"""
|
||||||
|
if not authenticate_token(token):
|
||||||
|
raise InvalidException(TOKEN_ERROR)
|
||||||
|
# 检查文件类型是否为xls
|
||||||
|
if xls_file.content_type not in ["application/vnd.ms-excel",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]:
|
||||||
|
resp = {"status": False, "message": "仅支持上传xls文件"}
|
||||||
|
else:
|
||||||
|
file_content = await xls_file.read()
|
||||||
|
max_size_mb = 10 # 设置最大允许的文件大小为 10MB
|
||||||
|
if xls_file.file.seek(0, os.SEEK_END) > max_size_mb * 1024 * 1024:
|
||||||
|
resp = {"status": False, "message": f"文件大小不得超过 {max_size_mb}MB"}
|
||||||
|
else:
|
||||||
|
# 解析xls,校验数据格式是否符合标准
|
||||||
|
required_columns = ["房屋id", "子系统id", "子系统空间名称"]
|
||||||
|
_callback, message, data = valid_xls(BytesIO(file_content), required_columns)
|
||||||
|
if _callback:
|
||||||
|
th = get_table_handler()
|
||||||
|
SpacesTable.update(th, data)
|
||||||
|
resp = {"status": True}
|
||||||
|
else:
|
||||||
|
raise InvalidException(message)
|
||||||
|
logger.Logger.debug(f"{request.url.path} {resp}")
|
||||||
|
return resp
|
||||||
1
SCLP/run.sh
Normal file
1
SCLP/run.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
nohup python main.py >> nohup.out 2>&1 &
|
||||||
85
SCLP/utils/database.py
Normal file
85
SCLP/utils/database.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 数据库处理基础方法
|
||||||
|
"""
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from config import DB_PATH
|
||||||
|
from utils.logger import Logger, new_dc
|
||||||
|
|
||||||
|
|
||||||
|
class SQLiteDatabaseEngine:
|
||||||
|
def __init__(self, db_path: str = "demo.db") -> None:
|
||||||
|
self.sqlite3 = sqlite3
|
||||||
|
self.db_path = db_path
|
||||||
|
self.connection = None
|
||||||
|
self.cursor = None
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""连接SQLite 数据库(如果数据库不存在则会自动创建)"""
|
||||||
|
self.connection = self.sqlite3.connect(self.db_path)
|
||||||
|
self.cursor = self.connection.cursor()
|
||||||
|
Logger.init(new_dc(f"🔗 SQLite - {self.db_path} has connect successfully! 🔗", "[1;32m"))
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
try:
|
||||||
|
self.cursor.close()
|
||||||
|
self.connection.close()
|
||||||
|
except Exception as e:
|
||||||
|
if type(e).__name__ != "ProgrammingError":
|
||||||
|
Logger.error(f"{type(e).__name__}, {e}")
|
||||||
|
Logger.info(new_dc(f"🔌 Disconnect from SQLite - {self.db_path}! 🔌", "[1m"))
|
||||||
|
|
||||||
|
def exist(self, table_name):
|
||||||
|
self.cursor.execute(
|
||||||
|
f"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
||||||
|
(table_name,),
|
||||||
|
)
|
||||||
|
result = self.cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTable:
|
||||||
|
def __init__(self, connection=None, cursor=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.cursor = cursor
|
||||||
|
|
||||||
|
def set(self, connection, cursor):
|
||||||
|
self.connection = connection
|
||||||
|
self.cursor = cursor
|
||||||
|
|
||||||
|
def execute(self, sql: str | list, params: tuple | list = ()):
|
||||||
|
if isinstance(sql, list):
|
||||||
|
for i, s in enumerate(sql):
|
||||||
|
self.cursor.execute(s, params[i])
|
||||||
|
else:
|
||||||
|
self.cursor.execute(sql, params)
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
def executemany(self, sql: str, params: list[tuple]):
|
||||||
|
self.cursor.executemany(sql, params)
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
def query(self, sql: str, params: tuple = ()):
|
||||||
|
self.cursor.execute(sql, params)
|
||||||
|
|
||||||
|
|
||||||
|
class TableHandlerSingleton:
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
db = SQLiteDatabaseEngine(db_path=DB_PATH)
|
||||||
|
cls._instance = BaseTable(db.connection, db.cursor)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_handler():
|
||||||
|
return TableHandlerSingleton.get_instance()
|
||||||
163
SCLP/utils/logger.py
Normal file
163
SCLP/utils/logger.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 日志模块
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
|
||||||
|
DEBUG = None
|
||||||
|
LOGGER_PATH = None
|
||||||
|
TOKEN_ERROR = "token无效"
|
||||||
|
|
||||||
|
|
||||||
|
def log(text: str, log_path: str = None):
|
||||||
|
"""打印日志"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_line = '[{}] {}'.format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), text)
|
||||||
|
if log_path:
|
||||||
|
log_file = open(log_path, 'a', encoding='utf8')
|
||||||
|
log_file.write("{}\n".format(log_line))
|
||||||
|
log_file.close()
|
||||||
|
print(log_line)
|
||||||
|
|
||||||
|
|
||||||
|
def log_plus(text: str, log_path: str = None, prefix_text: str = None, suffix_text: str = None):
|
||||||
|
"""加强版打印日志,预置了不同文字颜色"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
if prefix_text:
|
||||||
|
log_line_start = f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}] {prefix_text}"
|
||||||
|
else:
|
||||||
|
log_line_start = f"[{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}]"
|
||||||
|
if suffix_text:
|
||||||
|
log_line = f"{log_line_start} {text} {suffix_text}"
|
||||||
|
else:
|
||||||
|
log_line = f"{log_line_start} {text}"
|
||||||
|
if log_path:
|
||||||
|
log_file = open(log_path, 'a', encoding='utf8')
|
||||||
|
log_file.write("{}\n".format(log_line))
|
||||||
|
log_file.close()
|
||||||
|
print(log_line)
|
||||||
|
|
||||||
|
|
||||||
|
def format_text(text_list: list, align_list: list = None) -> str:
|
||||||
|
"""格式化一些文本信息, 可以根据 align_list 调整每列文本的距离"""
|
||||||
|
if not align_list:
|
||||||
|
align_list = [(30, '<'), (60, '<')]
|
||||||
|
formatted_text = []
|
||||||
|
for txt, (int_align, flag) in zip(text_list, align_list):
|
||||||
|
formatted_text.append(f'{txt:{flag}{int_align}}')
|
||||||
|
return ' '.join(formatted_text)
|
||||||
|
|
||||||
|
|
||||||
|
def new_dc(msgs: str | float | int, fore_color: str = "", back_color: str = "") -> str:
|
||||||
|
"""给文本上色
|
||||||
|
|
||||||
|
fore_color格式如下:
|
||||||
|
[{显示方式};{前景色};{背景色}m
|
||||||
|
|
||||||
|
显示方式
|
||||||
|
0(默认值)、1(高亮)、22(非粗体)、4(下划线)、24(非下划线)、 5(闪烁)、25(非闪烁)、7(反显)、27(非反显)
|
||||||
|
前景色
|
||||||
|
30(黑色)、31(红色)、32(绿色)、 33(黄色)、34(蓝色)、35(洋 红)、36(青色)、37(白色)
|
||||||
|
背景色
|
||||||
|
40(黑色)、41(红色)、42(绿色)、 43(黄色)、44(蓝色)、45(洋 红)、46(青色)、47(白色)
|
||||||
|
如:
|
||||||
|
高亮绿色红色背景 [1;32;41m
|
||||||
|
默认-绿字体 [32m
|
||||||
|
"""
|
||||||
|
if fore_color == "":
|
||||||
|
fore_color = '[32m' # 默认绿色
|
||||||
|
return "\033{}{}{}\033[0m".format(back_color, fore_color, str(msgs))
|
||||||
|
|
||||||
|
|
||||||
|
def console_mute(func):
|
||||||
|
"""采用装饰器的方式提供函数的控制台标准输出临时置空,净化控制台环境"""
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
original_stdout = sys.stdout
|
||||||
|
try:
|
||||||
|
sys.stdout = io.StringIO()
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
# 恢复原始的 sys.stdout 和 sys.stderr
|
||||||
|
sys.stdout = original_stdout
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def log_speed_ms(start_time: float, suffix: str = "", prefix: str = "", number_color: str = "", decimal: int = 4):
|
||||||
|
"""控制台打印消耗的毫秒时间"""
|
||||||
|
log(f"{suffix}用时:{new_dc(str(round((time.time() - start_time) * 1000, decimal)), number_color)}ms{prefix}")
|
||||||
|
|
||||||
|
|
||||||
|
def speed_ms(start_time: float, decimal: int = 4):
|
||||||
|
"""消耗的毫秒时间"""
|
||||||
|
return round((time.time() - start_time) * 1000, decimal)
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""对控制台日志输出的二次封装,对部分常用日志进行预置"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def debug(text: str, log_path: str = None):
|
||||||
|
"""预置的debug形式的log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
if isinstance(DEBUG, bool) and DEBUG:
|
||||||
|
log_plus(text, log_path, f"[{new_dc('INFO-DEBUG', '[34m')}]")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def info(text: str, log_path: str = None):
|
||||||
|
"""预置的info形式的log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(text, log_path, f"[{new_dc('INFO', '[34m')}]")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def error(text: str, log_path: str = None):
|
||||||
|
"""预置的error形式的log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(text, log_path, f"[{new_dc('ERROR', '[1;31m')}]")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def warn(text: str, log_path: str = None):
|
||||||
|
"""预置的warn形式的log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(text, log_path, f"[{new_dc('WARN', '[1;33m')}]")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init(text: str, log_path: str = None):
|
||||||
|
"""预置的error形式的log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(text, log_path, f"[{new_dc('INIT', '[35m')}]")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def title(text: str, log_path: str = None):
|
||||||
|
"""预置title形式的显目log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(new_dc(text, '[1m'), log_path, "🚀", "🚀")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def complete(text: str, log_path: str = None):
|
||||||
|
"""预置complete形式的显目log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(new_dc(text, '[1;32m'), log_path, "✅", "✅")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove(text: str, log_path: str = None):
|
||||||
|
"""预置remove形式的显目log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(new_dc(text, '[1m'), log_path, "🚮", "🚮")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def connect(text: str, log_path: str = None):
|
||||||
|
"""预置connect形式的显目log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(new_dc(text, '[1;32m'), log_path, "🔗", "🔗")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def disconnect(text: str, log_path: str = None):
|
||||||
|
"""预置disconnect形式的显目log"""
|
||||||
|
log_path = log_path if log_path else LOGGER_PATH
|
||||||
|
log_plus(new_dc(text, '[1m'), log_path, "🚫", "🚫")
|
||||||
360
SCLP/utils/misc.py
Normal file
360
SCLP/utils/misc.py
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author : xuxingchen
|
||||||
|
@Contact : xuxingchen@sinochem.com
|
||||||
|
@Desc : 杂项
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import warnings
|
||||||
|
from io import BytesIO
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import threading
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import socket
|
||||||
|
import psutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
from PIL import Image
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
from openpyxl.styles import PatternFill, Font, Border, Side
|
||||||
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
||||||
|
from openpyxl.workbook import Workbook
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from utils import logger
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip_address(interface_name: str) -> tuple[str, str]:
|
||||||
|
interfaces = psutil.net_if_addrs()
|
||||||
|
interface = interfaces.get(interface_name, None)
|
||||||
|
ipv4_address, ipv6_address = "", ""
|
||||||
|
if interface:
|
||||||
|
for address in interface:
|
||||||
|
if address.family == socket.AF_INET:
|
||||||
|
ipv4_address = address.address
|
||||||
|
elif address.family == socket.AF_INET6 and not address.address.startswith("fe80"):
|
||||||
|
ipv6_address = address.address
|
||||||
|
return ipv4_address, ipv6_address
|
||||||
|
|
||||||
|
def extract_fixed_length_number(number_str: str, fixed_length: int = 2) -> str:
|
||||||
|
"""提取一串存在数值的字符串中的第一个数值,对其从右往左取值定值,不足补0"""
|
||||||
|
pattern = re.compile(r'[0-9]+')
|
||||||
|
number = pattern.search(number_str)
|
||||||
|
if number is None:
|
||||||
|
return "0" * fixed_length
|
||||||
|
else:
|
||||||
|
if len(number[0]) < fixed_length:
|
||||||
|
return "0" * (fixed_length - len(number[0])) + number[0]
|
||||||
|
else:
|
||||||
|
return number[0][-fixed_length:]
|
||||||
|
|
||||||
|
|
||||||
|
def snake2camel(key: str) -> str:
|
||||||
|
"""snake命名风格转成camel命名风格"""
|
||||||
|
parts = key.split('_')
|
||||||
|
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def snake2camel_list_dict(snake_list: list[dict]) -> list[dict]:
|
||||||
|
"""将list[dict]中所有的snake格式的key转换成camel格式的key命名风格"""
|
||||||
|
camel_list = []
|
||||||
|
for snake_dict in snake_list:
|
||||||
|
camel_dict = {}
|
||||||
|
for key, value in snake_dict.items():
|
||||||
|
camel_dict[snake2camel(key)] = value
|
||||||
|
camel_list.append(camel_dict)
|
||||||
|
return camel_list
|
||||||
|
|
||||||
|
|
||||||
|
def clear_log_file(log_path: str, day: int = 7):
|
||||||
|
"""每7天清除一次日志文件"""
|
||||||
|
creation_time = os.path.getctime(log_path)
|
||||||
|
days_since_creation = (time.time() - creation_time) / (60 * 60 * 24)
|
||||||
|
if os.path.exists(log_path) and days_since_creation >= day:
|
||||||
|
try:
|
||||||
|
f0 = open(log_path, "r", encoding="utf8")
|
||||||
|
f1 = open(f"{log_path}.old", "w", encoding="utf8")
|
||||||
|
f1.write(f0.read())
|
||||||
|
f1.close()
|
||||||
|
f0.close()
|
||||||
|
os.remove(log_path)
|
||||||
|
print(f"日志文件 {logger.LOGGER_PATH} 完成重置")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"日志文件 {logger.LOGGER_PATH} 重置失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_captcha_text(characters: int = 6) -> str:
|
||||||
|
"""生成指定长度的随机文本"""
|
||||||
|
letters_and_digits = string.ascii_letters + string.digits
|
||||||
|
return ''.join(random.choice(letters_and_digits) for _ in range(characters))
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_number(phone_number: str, key: bytes = b'7A') -> str:
|
||||||
|
"""将电话号码加密为字符串"""
|
||||||
|
phone_bytes = phone_number.encode('utf-8') # 将字符串转换为字节
|
||||||
|
encrypted_bytes = bytes(
|
||||||
|
[byte ^ key[i % len(key)] for i, byte in enumerate(phone_bytes)]
|
||||||
|
)
|
||||||
|
# 使用Base64编码加密后的字节
|
||||||
|
encrypted_number = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||||
|
return encrypted_number
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_number(encrypted_number: str, key: bytes = b'7A') -> str:
|
||||||
|
"""将字符串解密为电话号码"""
|
||||||
|
# 使用Base64解码加密后的字符串
|
||||||
|
encrypted_bytes = base64.b64decode(encrypted_number)
|
||||||
|
decrypted_bytes = bytes(
|
||||||
|
[byte ^ key[i % len(key)] for i, byte in enumerate(encrypted_bytes)]
|
||||||
|
)
|
||||||
|
decrypted_number = decrypted_bytes.decode('utf-8')
|
||||||
|
return decrypted_number
|
||||||
|
|
||||||
|
|
||||||
|
def now_tz_datetime(days: int = 0) -> str:
|
||||||
|
future_time = datetime.now() + timedelta(days=days)
|
||||||
|
return future_time.strftime("%Y-%m-%dT%H:%M:%S.") + f"{future_time.microsecond // 1000:03d}Z"
|
||||||
|
|
||||||
|
|
||||||
|
def now_datetime_nanosecond(days: int = 0) -> str:
|
||||||
|
future_time = datetime.now() + timedelta(days=days)
|
||||||
|
return future_time.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
|
||||||
|
|
||||||
|
def now_datetime_second(days: int = 0) -> str:
|
||||||
|
future_time = datetime.now() + timedelta(days=days)
|
||||||
|
return future_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
|
def millisecond_timestamp2tz(timestamp_13: str):
|
||||||
|
timestamp = int(timestamp_13) / 1000
|
||||||
|
dt_utc = datetime.fromtimestamp(timestamp, tz=pytz.UTC)
|
||||||
|
# 转换为所需的时区,这里以北京时间(China Standard Time)为例
|
||||||
|
china_tz = pytz.timezone('Asia/Shanghai')
|
||||||
|
return dt_utc.astimezone(china_tz).strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt_utc.microsecond // 1000:03d}Z"
|
||||||
|
|
||||||
|
|
||||||
|
def is_image_url_valid(url: str) -> bool:
|
||||||
|
try:
|
||||||
|
# 发送请求获取URL内容
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status() # 如果状态码不是200,会抛出异常
|
||||||
|
|
||||||
|
# 将内容加载为图片
|
||||||
|
image = Image.open(BytesIO(response.content))
|
||||||
|
image.verify() # 验证图像文件是否可读
|
||||||
|
|
||||||
|
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
|
||||||
|
return True
|
||||||
|
except (requests.RequestException, IOError):
|
||||||
|
# 如果有任何异常,说明图片不可用或格式不可读
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_image_valid(path: str) -> bool:
|
||||||
|
try:
|
||||||
|
# 将内容加载为图片
|
||||||
|
image = Image.open(open(path, 'rb'))
|
||||||
|
image.verify() # 验证图像文件是否可读
|
||||||
|
|
||||||
|
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
|
||||||
|
return True
|
||||||
|
except (requests.RequestException, IOError):
|
||||||
|
# 如果有任何异常,说明图片不可用或格式不可读
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_md5(file_path):
|
||||||
|
content = open(file_path, 'rb')
|
||||||
|
md5hash = hashlib.md5(content.read())
|
||||||
|
return md5hash.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def sql_export_xls(query,
|
||||||
|
db_connection,
|
||||||
|
save_file_path,
|
||||||
|
sheet_title,
|
||||||
|
sheet_header: Optional[list] = None,
|
||||||
|
header_background_color: str = "808080",
|
||||||
|
header_font_color: str = "ffffff"):
|
||||||
|
df = pd.read_sql_query(query, db_connection)
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = sheet_title
|
||||||
|
for i, r in enumerate(dataframe_to_rows(df, index=False, header=True)):
|
||||||
|
if sheet_header is not None and i == 0:
|
||||||
|
ws.append(sheet_header)
|
||||||
|
continue
|
||||||
|
ws.append(r)
|
||||||
|
# 表头样式
|
||||||
|
header_fill = PatternFill(start_color=header_background_color, fill_type="solid")
|
||||||
|
header_font = Font(color=header_font_color, bold=True)
|
||||||
|
for cell in ws[1]:
|
||||||
|
cell.fill = header_fill
|
||||||
|
cell.font = header_font
|
||||||
|
# 边框样式 表头无边框-数据无顶实线框
|
||||||
|
thin_border = Border(
|
||||||
|
left=Side(style='thin'),
|
||||||
|
right=Side(style='thin'),
|
||||||
|
bottom=Side(style='thin')
|
||||||
|
)
|
||||||
|
for i, row in enumerate(ws.iter_rows()):
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
for cell in row:
|
||||||
|
cell.border = thin_border
|
||||||
|
# 单元格宽度自适应调整
|
||||||
|
for column in ws.columns:
|
||||||
|
max_length = 0
|
||||||
|
column = list(column)
|
||||||
|
for cell in column:
|
||||||
|
if cell.value is not None:
|
||||||
|
cell_length = len(str(cell.value))
|
||||||
|
if re.search(r'[\u4e00-\u9fff]', str(cell.value)):
|
||||||
|
cell_length += len(re.findall(r'[\u4e00-\u9fff]', str(cell.value)))
|
||||||
|
if cell_length > max_length:
|
||||||
|
max_length = cell_length
|
||||||
|
adjusted_width = (max_length + 2)
|
||||||
|
ws.column_dimensions[column[0].column_letter].width = adjusted_width
|
||||||
|
wb.save(save_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def valid_xls(file: BytesIO, required_columns: Optional[list]) -> tuple[bool, str, Optional[list]]:
|
||||||
|
"""如果校验通过返回list结构的数据,如果检验不通过返回None"""
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
try:
|
||||||
|
df = pd.read_excel(file)
|
||||||
|
df = df.replace(np.nan, None)
|
||||||
|
if all(col in df.columns for col in required_columns):
|
||||||
|
return True, "", df[required_columns].values.tolist()
|
||||||
|
else:
|
||||||
|
missing_cols = [col for col in required_columns if col not in df.columns]
|
||||||
|
return False, f"缺少必要的列: {', '.join(missing_cols)}", None
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"文件解析失败 {type(e).__name__}, {e}", None
|
||||||
|
|
||||||
|
|
||||||
|
class BasicCallback(BaseModel):
|
||||||
|
status: bool
|
||||||
|
message: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidException(Exception):
|
||||||
|
def __init__(self, message: str):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class UserData:
|
||||||
|
def __init__(self):
|
||||||
|
self.table_handle = None
|
||||||
|
self.topic: Optional[str] = None
|
||||||
|
self.topics: list = []
|
||||||
|
self.table_handler = None
|
||||||
|
self.message = None
|
||||||
|
self.token = None
|
||||||
|
self.status: dict = {}
|
||||||
|
self.clients: dict = {}
|
||||||
|
self.lock = threading.Lock() # 添加一个锁用于线程同步
|
||||||
|
|
||||||
|
def set_table_handle(self, value):
|
||||||
|
with self.lock:
|
||||||
|
self.table_handle = value
|
||||||
|
|
||||||
|
def set_topic(self, value: str):
|
||||||
|
with self.lock:
|
||||||
|
self.topic = value
|
||||||
|
|
||||||
|
def set_topics(self, value: list):
|
||||||
|
with self.lock:
|
||||||
|
self.topics = value
|
||||||
|
|
||||||
|
def set_table_handler(self, value):
|
||||||
|
with self.lock:
|
||||||
|
self.table_handler = value
|
||||||
|
|
||||||
|
def set_message(self, value):
|
||||||
|
with self.lock:
|
||||||
|
self.message = value
|
||||||
|
|
||||||
|
def set_token(self, value):
|
||||||
|
with self.lock:
|
||||||
|
self.token = value
|
||||||
|
|
||||||
|
def set_status(self, value: dict):
|
||||||
|
with self.lock:
|
||||||
|
self.status = value
|
||||||
|
|
||||||
|
def set_status_add(self, key, value):
|
||||||
|
with self.lock:
|
||||||
|
self.status[key] = value
|
||||||
|
|
||||||
|
def set_status_remove(self, key):
|
||||||
|
with self.lock:
|
||||||
|
if self.status and key in self.status.keys():
|
||||||
|
self.status.pop(key)
|
||||||
|
|
||||||
|
def get_status(self, key):
|
||||||
|
if self.status and key in self.status.keys():
|
||||||
|
return self.status[key]
|
||||||
|
|
||||||
|
def set_clients(self, value: dict):
|
||||||
|
with self.lock:
|
||||||
|
self.clients = value
|
||||||
|
|
||||||
|
def set_client_add(self, key, value):
|
||||||
|
with self.lock:
|
||||||
|
self.clients[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
def create_mqtt_client(broker_host,
|
||||||
|
broker_port,
|
||||||
|
userdata: UserData,
|
||||||
|
on_message=None,
|
||||||
|
on_publish=None,
|
||||||
|
on_connect=None,
|
||||||
|
on_disconnect=None,
|
||||||
|
client_id: str = "",
|
||||||
|
username: str = "",
|
||||||
|
password: str = ""):
|
||||||
|
if client_id != "":
|
||||||
|
client = mqtt.Client(client_id=client_id)
|
||||||
|
else:
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.user_data_set(userdata)
|
||||||
|
if on_connect:
|
||||||
|
client.on_connect = on_connect
|
||||||
|
if on_disconnect:
|
||||||
|
client.on_disconnect = on_disconnect
|
||||||
|
if on_message:
|
||||||
|
client.on_message = on_message
|
||||||
|
if on_publish:
|
||||||
|
client.on_publish = on_publish
|
||||||
|
client.username_pw_set(username, password)
|
||||||
|
client.connect(broker_host, broker_port)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
logger.Logger.init(logger.new_dc(f"🔗 Mqtt connection! {{rc: {rc}}} 🔗", '[1;32m'))
|
||||||
|
if userdata.topics:
|
||||||
|
_topics = [(topic, 0) for topic in userdata.topics]
|
||||||
|
client.subscribe(_topics)
|
||||||
|
logger.Logger.debug(f"subscribe topics: {userdata.topics}")
|
||||||
|
|
||||||
|
|
||||||
|
def on_disconnect(client, userdata, rc):
|
||||||
|
logger.Logger.info(logger.new_dc(f"🔌 Break mqtt connection! {{rc: {rc}}} 🔌", "[1m"))
|
||||||
|
|
||||||
|
|
||||||
|
def on_publish(client, userdata, rc):
|
||||||
|
logger.Logger.debug(f"{userdata.topic} <- {userdata.message}")
|
||||||
Loading…
x
Reference in New Issue
Block a user