Bläddra i källkod

update: 更新代码

Casper 5 månader sedan
förälder
incheckning
1e720df7b8
100 ändrade filer med 11212 tillägg och 0 borttagningar
  1. 259 0
      3rdparty/xapi/audio/api.py
  2. 91 0
      3rdparty/xapi/enfei/api.py
  3. 132 0
      3rdparty/xapi/google/api.py
  4. 123 0
      3rdparty/xapi/hik/api.py
  5. 92 0
      3rdparty/xapi/hik/read100.py
  6. BIN
      3rdparty/xapi/mccbts/1.txt
  7. 339 0
      3rdparty/xapi/mccbts/api.py
  8. 564 0
      3rdparty/xapi/nessus/api.py
  9. 124 0
      3rdparty/xapi/onvif/api.py
  10. 263 0
      3rdparty/xapi/ros1/api_for_hs.py
  11. 52 0
      3rdparty/xapi/ros1/cmd1001.txt
  12. 40 0
      3rdparty/xapi/ros1/cmd100101.txt
  13. 52 0
      3rdparty/xapi/ros1/s1-行驶-去e点
  14. 12 0
      3rdparty/xapi/ros1/s2-作业-叉包
  15. 11 0
      3rdparty/xapi/ros1/s3-作业-回e点
  16. 31 0
      3rdparty/xapi/ros1/s4-行驶-e点-到-倒渣口2
  17. 18 0
      3rdparty/xapi/ros1/s5-行驶-e点-到-倒渣口2
  18. 43 0
      3rdparty/xapi/ros1/s6-行驶-倒渣口2-到-n20的e点
  19. 12 0
      3rdparty/xapi/ros1/s7-作业-放包
  20. 4 0
      3rdparty/xapi/taiwuict/u1_for_aibox.py
  21. 4 0
      3rdparty/xapi/taiwuict/u2_for_aibox.py
  22. 833 0
      3rdparty/xapi/taiwuict/u3_for_cscec.py
  23. 222 0
      3rdparty/xclient/xinflux.py
  24. 93 0
      3rdparty/xclient/xmaria.py
  25. 508 0
      3rdparty/xclient/xmongo.py
  26. 19 0
      3rdparty/xclient/xmqtt-1.py
  27. 89 0
      3rdparty/xclient/xmqtt.py
  28. 158 0
      3rdparty/xclient/xmysql.py
  29. 116 0
      3rdparty/xclient/xqcloudsms.py
  30. 310 0
      3rdparty/xclient/xredis.py
  31. 42 0
      3rdparty/xclient/xsmtp.py
  32. 76 0
      3rdparty/xclient/xsmtp_zl.py
  33. 199 0
      3rdparty/xclient/xssh.py
  34. 31 0
      3rdparty/xclient/xtcp.py
  35. 48 0
      3rdparty/xclient/xudp.py
  36. 39 0
      3rdparty/xdecorator.py
  37. 306 0
      3rdparty/xengine/cv_face_recognition/engine.py
  38. 10 0
      3rdparty/xlib/__init__.py
  39. 51 0
      3rdparty/xlib/xbase64.py
  40. 248 0
      3rdparty/xlib/xfile.py
  41. 26 0
      3rdparty/xlib/xipv4.py
  42. 28 0
      3rdparty/xlib/xlist.py
  43. 26 0
      3rdparty/xlib/xlog.py
  44. 54 0
      3rdparty/xlib/xmarkdwon.py
  45. 93 0
      3rdparty/xlib/xpickle.py
  46. 27 0
      3rdparty/xlib/xsubprocess.py
  47. 44 0
      3rdparty/xlib/xthread.py
  48. 169 0
      3rdparty/xlib/xtime.py
  49. 16 0
      3rdparty/xlib/xuuid.py
  50. 406 0
      3rdparty/xpip/aes_by_crypto.py
  51. 65 0
      3rdparty/xpip/camera_by_cv2.py
  52. 40 0
      3rdparty/xpip/data_by_numpy.py
  53. 30 0
      3rdparty/xpip/show_by_prettytable.py
  54. 252 0
      3rdparty/xpip/xapscheduler.py
  55. 189 0
      3rdparty/xpip/zip_by_pyminizip.py
  56. 82 0
      3rdparty/xplugin/hydra/plugin.py
  57. 260 0
      3rdparty/xplugin/msscan/plugin.py
  58. 59 0
      3rdparty/xserver/xtcp.py
  59. 61 0
      3rdparty/xserver/xudp.py
  60. 92 0
      sri-server-bg01/Dockerfile
  61. 37 0
      sri-server-bg01/README-usage.bash
  62. 125 0
      sri-server-bg01/README-usage.md
  63. 19 0
      sri-server-bg01/README-usage.txt
  64. 200 0
      sri-server-bg01/api/api.py
  65. 298 0
      sri-server-bg01/api/v6/code1000.py
  66. 240 0
      sri-server-bg01/api/v6/code2000.py
  67. 131 0
      sri-server-bg01/api/v6/code3000.py
  68. 24 0
      sri-server-bg01/app.py
  69. 22 0
      sri-server-bg01/build.Dockerfile
  70. 37 0
      sri-server-bg01/compose.yml
  71. 54 0
      sri-server-bg01/cythonize.py
  72. 101 0
      sri-server-bg01/default_data.py
  73. 72 0
      sri-server-bg01/default_data_insert.py
  74. 35 0
      sri-server-bg01/hub.py
  75. 158 0
      sri-server-bg01/key/v1.py
  76. 10 0
      sri-server-bg01/main.py
  77. 82 0
      sri-server-bg01/main.spec
  78. 8 0
      sri-server-bg01/run-c.sh
  79. 10 0
      sri-server-bg01/run-exe-wrap.sh
  80. 6 0
      sri-server-bg01/run-exe.sh
  81. 7 0
      sri-server-bg01/run.sh
  82. 127 0
      sri-server-bg01/test/test-1000.py
  83. 106 0
      sri-server-bg01/test/test-2000.py
  84. 49 0
      sri-server-bg01/test/test-3000.py
  85. 39 0
      sri-server-bg01/test/接口文档(第2批,共5个,已部署5个).txt
  86. 39 0
      sri-server-bg02/Dockerfile
  87. 8 0
      sri-server-bg02/README-usage.bash
  88. 67 0
      sri-server-bg02/api/Command.py
  89. 58 0
      sri-server-bg02/api/Key.py
  90. 515 0
      sri-server-bg02/api/TEST.py
  91. 222 0
      sri-server-bg02/api/Task.py
  92. 36 0
      sri-server-bg02/api/url.py
  93. 37 0
      sri-server-bg02/compose.yml
  94. 17 0
      sri-server-bg02/hub.py
  95. 46 0
      sri-server-bg02/lib/JobManage.py
  96. 49 0
      sri-server-bg02/lib/MessageListener.py
  97. 252 0
      sri-server-bg02/lib/line_manage.py
  98. 159 0
      sri-server-bg02/lib/sound_columns.py
  99. 22 0
      sri-server-bg02/main.py
  100. 5 0
      sri-server-bg02/run.sh

+ 259 - 0
3rdparty/xapi/audio/api.py

@@ -0,0 +1,259 @@
+# update: 2021-10-15
+"""
+配置流程
+    1、禁用vbox虚拟机的网卡。
+    2、pc端安装客户端程序,打开“智能广播扫描工具”,扫描设备,并修改设备ip。(服务ip不能和设备ip一样,随意就可以)
+
+{
+    "sn": "iHciceH9A5",
+    "type": "req",
+    "name": "songs_queue_append",
+    "params": {
+        "tid": "134",
+        "vol": 100,
+        "urls": [
+            {
+                "name": "1",
+                # 只支持mp3格式 不支持wav格式
+                "uri": "http://websitecdn.gangganghao.com.cn/ls20/mp3/ei.mp3"
+            },
+            {
+                "name": "2",
+                "uri": "http://websitecdn.gangganghao.com.cn/ls20/mp3/jxy.mp3"
+            }
+        ]
+    }
+}
+"""
+import requests
+import time
+
+
+class Api(object):
+
+    def __init__(self, service_url='http://192.168.30.89:8888'):
+        """
+        port: 端口号 8888 安装目录下 ls20_mgrserver/ls20_mgrserver.ini 文件中进行配置
+
+        192.168.1.241 ls20://02029F15B65A
+        192.168.1.242 ls20://0203B0F822FC
+        192.168.1.243 ls20://02015790B99B
+        192.168.1.244 ls20://02006723C3BA
+        192.168.1.245 ls20://020099728112
+        """
+        # self.api_service_url = service_url
+
+        # --- 八局项目 第3现场设备 ---
+        # self.api_service_url = 'http://192.168.15.193:8888'
+        # self.file_service_url = 'http://192.168.15.195:9900'
+
+        # --- 八局项目 第4现场设备 ---
+        self.api_service_url = 'http://192.168.1.43:8888'
+        self.file_service_url = 'http://192.168.1.242:9900'
+
+        self.headers = dict()
+        self.headers['content-type'] = 'application/json'
+
+    def call_audio_make_sound_v2(self, file_name, url, sn):
+        """
+        url: http://sc.geemi.cn/smartSiteService/api/uploadLog/uploadLogCapture
+        """
+        data = {
+            # "sn": sn,
+            "sn": 'iHciceH9A5',
+            "type": "req",
+            "name": 'songs_queue_append',
+            "params": {
+                "tid": '1',
+                "vol": 50,
+                "urls": [
+                    {
+                        "name": "1",
+                        # "uri": f"http://127.0.0.1:5042/api?module=TEST&method=test011&mp3_name={file_name}",
+                        # "uri": "http://192.168.20.231:8802/api?mp3_name=2021-10-16-11-05-29-717030.mp3",
+                        # "uri": f"http://fra-component-trigger:5042/api?mp3_name={file_name}",
+                        # "uri": f"http://192.168.20.231:9900/{file_name}",
+                        # "uri": f"http://192.168.0.15:9900/{file_name}",
+                        # "uri": f"http://192.168.51.242:9900/{file_name}",
+                        # "uri": "http://192.168.51.242:9900/find-unknown-person.mp3",
+                        # "uri": "http://192.168.15.195:9900/find-unknown-person.mp3",
+                        "uri": f"{self.file_service_url}/find-unknown-person.mp3",
+                    }
+                ]
+            }
+        }
+        times = 0
+        while times < 10:
+
+            try:
+
+                print('Api:call_audio_make_sound_v2:url:', self.api_service_url)
+                print('Api:call_audio_make_sound_v2:uri:', data.get('params').get('urls')[0].get('uri'))
+                response = requests.post(self.api_service_url, json=data, headers=self.headers)
+                print('Api:call_audio_make_sound_v2:result:', response.status_code)
+                return True
+
+            except Exception as e:
+
+                print(e)
+                times += 1
+                time.sleep(1)
+
+        return False
+
+    def call_audio_make_sound_v3(self, result_type):
+        """
+        url: http://sc.geemi.cn/smartSiteService/api/uploadLog/uploadLogCapture
+        result_type: 结果类型 0 匹配到人员 1 人脸检测失败 2 提取特征失败 3 未匹配到人员
+        """
+        if result_type in [3]:
+            # uri = "http://192.168.15.195:9900/a1.mp3"  # 请登记 3
+            uri = f"{self.file_service_url}/a1.mp3"  # 请登记 3
+        elif result_type in [1, 2]:
+            # uri = "http://192.168.15.195:9900/a2.mp3"  # 请重试 1 2
+            uri = f"{self.file_service_url}/a2.mp3"  # 请重试 1 2
+        elif result_type in [0]:
+            # uri = "http://192.168.15.195:9900/a3.mp3"  # 欢迎 0
+            uri = f"{self.file_service_url}/a3.mp3"  # 欢迎 0
+        else:
+            return False
+        data = {
+            # "sn": sn,
+            "sn": 'iHciceH9A5',
+            "type": "req",
+            "name": 'songs_queue_append',
+            "params": {
+                "tid": '1',
+                # "vol": 50,
+                "vol": 100,
+                "urls": [
+                    {
+                        "name": "1",
+                        # "uri": f"http://127.0.0.1:5042/api?module=TEST&method=test011&mp3_name={file_name}",
+                        # "uri": "http://192.168.20.231:8802/api?mp3_name=2021-10-16-11-05-29-717030.mp3",
+                        # "uri": f"http://fra-component-trigger:5042/api?mp3_name={file_name}",
+                        # "uri": f"http://192.168.20.231:9900/{file_name}",
+                        # "uri": f"http://192.168.0.15:9900/{file_name}",
+                        # "uri": f"http://192.168.51.242:9900/{file_name}",
+                        # "uri": "http://192.168.51.242:9900/find-unknown-person.mp3",
+                        # "uri": "http://192.168.15.195:9900/a1.mp3",
+                        "uri": uri,
+                    }
+                ]
+            }
+        }
+        times = 0
+        while times < 10:
+
+            try:
+
+                # print('Api:call_audio_make_sound_v3:url:', url)
+                # print('Api:call_audio_make_sound_v3:uri:', data.get('params').get('urls')[0].get('uri'))
+                response = requests.post(self.api_service_url, json=data, headers=self.headers)
+                # print('Api:call_audio_make_sound_v3:result:', response.status_code)
+                return True
+
+            except Exception as e:
+
+                print(e)
+                times += 1
+                time.sleep(1)
+
+        return False
+
+    def file_download(self):
+        """
+        doc: https://documenter.getpostman.com/view/691989/TzRVdR6Q#70870504-1cb6-439b-9cf4-f1f786d9d3a1
+        """
+        data = {
+            # "sn": ["ls20://0201518F79D4"],
+            "sn": ["iHciceH9A5"],
+            "type": "req",
+            "name": "file_download",
+            "params": {
+                "urls": [
+                    {
+                        "uri": "http://192.168.20.231:9900/2021-10-16-11-05-29-717030.mp3",
+                        "target": "/root/2021-10-16-11-05-29-717030.mp3"
+                    }
+                ]
+            }
+        }
+        print('Api:file_download:url:', self.api_service_url)
+        response = requests.post(self.api_service_url, json=data, headers=self.headers)
+        print('Api:file_download:result:', response.status_code)
+        return response.status_code
+
+    def songs_queue_append(self):
+        """
+        url: http://sc.geemi.cn/smartSiteService/api/uploadLog/uploadLogCapture
+        """
+        from requests.exceptions import ConnectionError, ReadTimeout
+        import time
+        time.sleep(0.01)
+
+        # url = "http://192.168.20.189:8888"
+        # url = "http://192.168.51.193:8888"
+        # url = "http://192.168.15.193:8888"
+        data = {
+            # "sn": "iHciceH9A5",  # 第3现场设备
+            "sn": "ls20://02009EDCD48C",  # 第4现场设备
+            "type": "req",
+            "name": "songs_queue_append",
+            "params": {
+                "tid": "134",
+                "vol": 80,
+                # "vol": 10,  # 基本听不见
+                "urls": [
+                    {
+                        "name": "1",
+                        "uri": f"{self.file_service_url}/a2.mp3",
+
+                        # --- test ---
+                        # "uri": "http://websitecdn.gangganghao.com.cn/ls20/mp3/ei.mp3",
+                        # "uri": "http://192.168.20.231:8802/api?mp3_name=2021-10-16-11-05-29-717030.mp3",
+                        # "uri": "http://192.168.20.231:9900/2021-10-16-11-05-29-717030.mp3",
+                        # "uri": "/root/2021-10-16-11-05-29-717030.mp3",
+                        # "uri": "http://192.168.0.15:9900/2021-10-16-11-05-29-717030.mp3",
+                        # "uri": "http://192.168.51.242:9900/find-unknown-person.mp3",
+                        # "uri": "http://192.168.15.195:9900/find-unknown-person.mp3",
+                        # "uri": "http://192.168.15.195:9900/a1.mp3",
+
+                    },
+                    # {
+                    #     "name": "2",
+                    #     "uri": "http://websitecdn.gangganghao.com.cn/ls20/mp3/jxy.mp3"
+                    # }
+                ]
+            }
+        }
+        run_at = time.time()
+        times = 0
+        while times < 10:
+
+            try:
+
+                print('Api:songs_queue_append:url:', self.api_service_url)
+                # response = requests.post(url, json=data, headers=self.headers, timeout=1)
+                response = requests.post(self.api_service_url, json=data, headers=self.headers)
+                print('Api:songs_queue_append:result:', response.status_code)
+                return f"{round(time.time() - run_at, 2)}s"
+
+            # except ConnectionError as e:
+            except Exception as e:
+
+                print(e)
+                times += 1
+                time.sleep(1)
+
+        return f"{round(time.time() - run_at, 2)}s"
+
+
+if __name__ == '__main__':
+    # --- init ---
+    api = Api()
+
+    # --- test --- cd /home/server/projects/taiwuict/cscec-8bur-vms/supplement-python/apis/audio && python3 api.py
+    # out = api.file_download()
+    out = api.songs_queue_append()
+    print('use time:', out)

+ 91 - 0
3rdparty/xapi/enfei/api.py

@@ -0,0 +1,91 @@
+# update: 2024-3-23
+"""
+配置hosts
+10.10.4.103 cloudos.service.ob.local
+10.62.115.83 harbor.platform.dyys.com
+10.62.115.11 hyty-ifactory.platform.dyys.com
+10.62.115.83 harbor.platform.dyys.com
+10.62.115.11 qbee.platform.dyys.com
+10.62.115.208  mdm.platform.dyys.com
+10.62.107.11 paas.pre.platform.dyys.com
+10.62.107.11 qbee.pre.platform.dyys.com
+10.62.115.11 dacoo.platform.dyys.com
+10.62.115.11 data-assets-process.platform.dyys.com
+10.62.115.11 bitmagic.platform.dyys.com
+"""
+import requests
+
+import sys
+import importlib
+import traceback
+
+# sys.path.append(r'E:\casper\repositories\repositories\casperz.py-project\module-py')  # for pc
+sys.path.append('/home/ubuntu/repositories/repositories/casperz.py-project/module-py')
+methods = importlib.import_module(f"xlib")
+
+
+class API(object):
+
+    # def __init__(self, username='System001', password='Swdx@143',
+    def __init__(self, username='', password=''):
+        """
+        """
+        self.url = f"http://hyty-ifactory.platform.dyys.com/gongyi/ui/ResiduePackage/driver/getParamsSet"
+
+    def get_pot_list(self):
+        """
+        获取渣罐数据
+        return [
+            {
+                'id': '1427897186149990401',
+                'localNumber': '434',  # 渣位号
+                'packageNumber': None,  # 渣包号
+                'localX': None,  # 渣位坐标X
+                'localY': None,  # 渣位坐标Y
+                'localStatus': 0,  # 渣位水管开关转态 0 关闭 非0 开启
+                'packageStatus': 1,  # 渣包状态 1 空位 2 就绪 3 缓冷(空冷) 4 水冷 5 自冷(水冷) 6 待倒 7 故障
+                'weight': None,  # 渣包重量
+                'createBy': 'admin',
+                'createTime': '2021-08-18',
+                'updateBy': 'admin',
+                'updateTime': '2024-03-23'
+            }
+        ]
+        return dict(code=-1, data=[], message=f"something is wrong. [{exception.__class__.__name__}]",
+                    details=f"{methods.trace_log()}")
+
+        """
+        try:
+            response = requests.get(url=self.url)
+            print('API:get_pot_list:url:', self.url)
+            print('API:get_pot_list:status_code:', response.status_code)
+            result = response.json().get('result')
+
+            # --- check ---
+            if not result:
+                reason = '接口数据异常'
+                detail = f"请求地址: {self.url}, 请求方式: GET, 返回结果: {response.text}"
+                return reason, detail
+
+            # --- fill pot_list ---
+            pot_list = list()
+            for index in range(len(result)):
+                pot_list += result[index]
+
+            return pot_list
+        except Exception as exception:
+
+            print(f"API.get_pot_list.exception: {exception.__class__.__name__}")
+            print(f"API.get_pot_list.traceback: {traceback.format_exc()}")
+            reason = f"{exception.__class__.__name__}"
+            detail = f"{traceback.format_exc()}"
+            return reason, detail
+
+
+if __name__ == '__main__':
+    # --- init ---
+    api = API()
+
+    # --- test ---
+    out = api.get_pot_list()
+    print(len(out))

+ 132 - 0
3rdparty/xapi/google/api.py

@@ -0,0 +1,132 @@
+"""
+谷歌翻译接口
+"""
+from urllib.parse import quote
+import requests
+import execjs
+import logging
+
+logger = logging.getLogger(__name__)
+code = """
+function TL(a) {
+    var k = "";
+    var b = 406644;
+    var b1 = 3293161072;
+    var jd = ".";
+    var $b = "+-a^+6";
+    var Zb = "+-3^+b+-f";
+    for (var e = [], f = 0, g = 0; g < a.length; g++) {
+        var m = a.charCodeAt(g);
+        128 > m ? e[f++] = m : (2048 > m ? e[f++] = m >> 6 | 192 : (55296 == (m & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (m = 65536 + ((m & 1023) << 10) + (a.charCodeAt(++g) & 1023),
+        e[f++] = m >> 18 | 240,
+        e[f++] = m >> 12 & 63 | 128) : e[f++] = m >> 12 | 224,
+        e[f++] = m >> 6 & 63 | 128),
+        e[f++] = m & 63 | 128)
+    }
+    a = b;
+    for (f = 0; f < e.length; f++) a += e[f],
+    a = RL(a, $b);
+    a = RL(a, Zb);
+    a ^= b1 || 0;
+    0 > a && (a = (a & 2147483647) + 2147483648);
+    a %= 1E6;
+    return a.toString() + jd + (a ^ b)
+};
+function RL(a, b) {
+    var t = "a";
+    var Yb = "+";
+    for (var c = 0; c < b.length - 2; c += 3) {
+        var d = b.charAt(c + 2),
+        d = d >= t ? d.charCodeAt(0) - 87 : Number(d),
+        d = b.charAt(c + 1) == Yb ? a >>> d: a << d;
+        a = b.charAt(c) == Yb ? a + d & 4294967295 : a ^ d
+    }
+    return a
+}
+"""
+
+
+class Api(object):
+
+    def __init__(self):
+        self.ctx = execjs.compile(code)
+        self.headers = {
+            'authority': 'translate.google.cn',
+            'method': 'GET',
+            'path': '',
+            'scheme': 'https',
+            'accept': '*/*',
+            'accept-encoding': 'gzip, deflate, br',
+            'accept-language': 'zh-CN,zh;q=0.9,ja;q=0.8',
+            # 'cookie': '_ga=GA1.3.110668007.1547438795; _gid=GA1.3.791931751.1548053917; 1P_JAR=2019-1-23-1; NID=156=biJbQQ3j2gPAJVBfdgBjWHjpC5m9vPqwJ6n6gxTvY8n1eyM8LY5tkYDRsYvacEnWNtMh3ux0-lUJr439QFquSoqEIByw7al6n_yrHqhFNnb5fKyIWMewmqoOJ2fyNaZWrCwl7MA8P_qqPDM5uRIm9SAc5ybSGZijsjalN8YDkxQ',
+            'cookie': '_ga=GA1.3.110668007.1547438795; _gid=GA1.3.1522575542.1548327032; 1P_JAR=2019-1-24-10; NID=156=ELGmtJHel1YG9Q3RxRI4HTgAc3l1n7Y6PAxGwvecTJDJ2ScgW2p-CXdvh88XFb9dTbYEBkoayWb-2vjJbB-Rhf6auRj-M-2QRUKdZG04lt7ybh8GgffGtepoA4oPN9OO9TeAoWDY0HJHDWCUwCpYzlaQK-gKCh5aVC4HVMeoppI',
+            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64)  AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36',
+            'x-client-data': 'CKi1yQEIhrbJAQijtskBCMG2yQEIqZ3KAQioo8oBCL+nygEI7KfKAQjiqMoBGPmlygE='
+        }
+
+    def get_tk(self, text):
+        return self.ctx.call("TL", text)
+
+    @staticmethod
+    def build_url(text, tk, tl='zh-CN'):
+        """
+        需要用转URLEncoder
+        :param text:
+        :param tk:
+        :param tl:
+        :return:
+        """
+        return f"https://translate.google.cn/translate_a/single?client=webapp&sl=auto&tl={tl}&hl=zh-CN&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&source=btn&ssel=0&tsel=0&kc=0&tk={tk}&q={quote(text, encoding='utf-8')}"
+
+    def translate(self, text, output_type='zh-CN'):
+        """
+        language_type为要翻译的语言
+        de:德语
+        ja:日语
+        sv:瑞典语
+        nl:荷兰语
+        ar:阿拉伯语
+        ko:韩语
+        pt:葡萄牙语
+        zh-CN:中文简体
+        zh-TW:中文繁体
+        """
+        # url = 'https://translate.google.cn/translate_a/single'
+        # data = {
+        # 	'client': 'webapp',
+        # 	'sl': 'auto',
+        # 	'tl': output_type,
+        # 	'hl': 'zh-CN',
+        # 	'dt': 'at',
+        # 	'source': 'btn',
+        # 	'ssel': '0',
+        # 	'tsel': '0',
+        # 	'kc': '0',
+        # 	'tk': self.get_tk(text),
+        # 	'q': quote(text, encoding='utf-8'),
+        # }
+        url = self.build_url(text, self.get_tk(text), output_type)
+        try:
+            logger.info(f"GoogleApi.translate:request: {url}")
+            # response = requests.get(url=url, headers=self.headers, params=data)
+            response = requests.get(url=url, headers=self.headers)
+            logger.info(f"GoogleApi.translate:response: {response.status_code}")
+        except Exception as e:
+            import traceback
+            logger.info(f"GoogleApi.translate:error: {e.__class__.__name__}")
+            logger.info(f"GoogleApi.translate:details: {traceback.format_exc()}")
+            return ''
+        if response.status_code > 300:
+            logger.info(f"GoogleApi.translate:status: {response.status_code}")
+            logger.info(f"GoogleApi.translate:reason: {response.reason}")
+            logger.info(f"GoogleApi.translate:result: {response.text}")
+            return ''
+        else:
+            return response.json()[0][0][0]
+
+
+if __name__ == '__main__':
+    word = '** RESERVED ** This candidate has been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided.'
+    api = Api()
+    translate_result = api.translate(word)
+    print(translate_result)

+ 123 - 0
3rdparty/xapi/hik/api.py

@@ -0,0 +1,123 @@
+# update: 2022-6-24
+"""
+海康人脸摄像机接口
+
+    提供 read 1、read 100,两种方法
+"""
+import requests
+import datetime
+import time
+
+import sys
+import importlib
+
+# sys.path.append('/home/server/projects/taiwuict/cscec-8bur-vms/supplement-python')
+sys.path.append(r'D:\share\gitee\taiwuict.cscec-8bur-vms\supplement-python')  # for pc
+methods = importlib.import_module(f"libraries.base_original")
+
+
+class API(object):
+
+    def __init__(self):
+        self.camera_ipv4 = None
+        self.camera_user = None
+        self.camera_pass = None
+        self.client = None
+        self.last_receive_at = None
+
+    def connect_camera(self, camera_ipv4, camera_user, camera_pass):
+        """
+        连接摄像机
+        """
+        self.camera_ipv4 = camera_ipv4
+        self.camera_user = camera_user
+        self.camera_pass = camera_pass
+        session = requests.session()
+        request_url = f'http://{self.camera_ipv4}:80/ISAPI/Event/notification/alertStream'  # 设置认证信息
+        auth = requests.auth.HTTPDigestAuth(self.camera_user, self.camera_pass)  # 发送请求,获取响应
+        self.client = session.get(request_url, auth=auth, verify=False, stream=True)
+
+    def throw(self, backdata, image_hex):
+        """
+        回传数据
+        """
+        if backdata is None:
+            return
+        if self.camera_ipv4 not in backdata:
+            backdata[self.camera_ipv4] = dict()
+        object_id = str(time.time())
+        backdata[self.camera_ipv4][object_id] = dict()
+        backdata[self.camera_ipv4][object_id]['tracking_is'] = False
+        backdata[self.camera_ipv4][object_id]['hex_image'] = image_hex
+
+    def read1(self, backdata=None):
+        """
+        """
+        # --- define ---
+        image_hex = str()
+        start_is = False
+        print_is = False
+        hex_start = ''
+        hex_end = ''
+
+        while True:
+
+            # --- get ---
+            line = self.client.raw.read(1)
+            line = line.hex()
+
+            # --- fill ---
+            now_at = time.time()
+            if not self.last_receive_at:
+                self.last_receive_at = now_at
+            if line:
+                self.last_receive_at = now_at
+
+            # --- fill ---
+            hex_start += line
+
+            # --- check ---
+            if len(hex_start) > 8:
+                hex_start = hex_start[-8:]
+
+            # --- check ---
+            if '0d0affd8' == hex_start:
+                start_is = True
+                image_hex = 'ff'
+                methods.debug_log('hikvision_detector',
+                                  f"m-44: 0d0affd8 is {datetime.datetime.now().strftime('%H:%M:%S.%f')}")
+
+            # --- fill ---
+            if start_is:
+                image_hex += line
+
+            # --- check ---
+            if start_is:
+
+                hex_end += line
+
+                if len(hex_end) > 8:
+                    hex_end = hex_end[-8:]
+
+                if 'ffd90d0a' == hex_end:
+                    print_is = True
+                    image_hex = image_hex[:-4]
+                    methods.debug_log('hikvision_detector',
+                                      f"m-44: ffd90d0a is {datetime.datetime.now().strftime('%H:%M:%S.%f')}")
+
+            # --- fill ---
+            if print_is:
+                self.throw(backdata, image_hex)
+                image_hex = str()
+                hex_start = str()
+                hex_end = str()
+                start_is = False
+                print_is = False
+
+
+if __name__ == '__main__':
+    # --- init ---
+    middledata = {}
+    agent = API()
+    agent.connect_camera(camera_ipv4='192.168.0.181', camera_user='admin', camera_pass='DEVdev123')
+    agent.read1({})

+ 92 - 0
3rdparty/xapi/hik/read100.py

@@ -0,0 +1,92 @@
+from hub import methods, Global
+from middledata import Middledata
+
+import requests
+import time
+
+
+def main(args):
+
+    while True:
+        try:
+            camera_ipv4, camera_user, camera_pass = args[1], args[2], args[3]
+            request_url = f'http://{camera_ipv4}:80/ISAPI/Event/notification/alertStream'  # 设置认证信息
+            auth = requests.auth.HTTPDigestAuth(camera_user, camera_pass)  # 发送请求,获取响应
+            session = requests.session()
+            response = session.get(request_url, auth=auth, verify=False, stream=True)
+
+            print(response.headers)
+            print(response.headers.get('content-type'))
+
+            image_hex = str()
+            start_is = False
+            print_is = False
+
+            while True:
+
+                # --- check ---
+                # now_at = time.time()
+                # if (now_at - run_at) > 300.0:  # todo 5分钟检测一次
+                #     run_at = now_at
+                #     while True:
+                #         rtsp = f"rtsp://{camera_user}:{camera_pass}@{camera_ipv4}:554/h264/ch1/main/av_stream"
+                #         cap = cv2.VideoCapture(rtsp)
+                #         ret, _ = cap.read()
+                #         if ret:
+                #             break
+                #         else:
+                #             methods.debug_log('hikvision_detector', f"m-40: wait 1 minutes try again!")
+                #             time.sleep(60)
+
+                # --- get ---
+                line = response.raw.read(100)
+                # line = response.raw.read(1024*1024)
+                # line = response.raw.read(1024*256)
+                # methods.debug_log('hikvision_detector', f"m-52: check at {methods.now_string()}")
+                # methods.debug_log('hikvision_detector', f"m-52: line | {line} | {type(line)}")
+
+                # --- check ---
+                if not line:
+                    continue
+
+                line = line.hex()
+
+                # --- check ---
+                if '0d0affd8' in line:
+                    # methods.debug_log('hikvision_detector', f"m-52: check start at {methods.now_string()}")
+                    start_is = True
+                    line = 'ffd8' + line.split('0d0affd8')[1]
+
+                # --- check ---
+                if 'ffd90d0a' in line:
+                    # methods.debug_log('hikvision_detector', f"m-52: check end at {methods.now_string()}")
+                    print_is = True
+                    line = line.split('ffd90d0a')[0] + 'ffd9'
+
+                # --- fill ---
+                if start_is:
+                    image_hex += line
+
+                # --- save ---
+                if print_is:
+                    # --- fill ---
+                    object_id = str(time.time())
+                    Middledata.target_dict[object_id] = dict()
+                    Middledata.target_dict[object_id]['tracking_is'] = False
+                    Middledata.target_dict[object_id]['hex_image'] = image_hex  # base64.b16decode(image_hex.upper())
+
+                    # --- update ---
+                    image_hex = str()
+                    start_is = False
+                    print_is = False
+
+        except Exception as exception:
+            methods.debug_log('hikvision_detector', f"m-64: exception | {exception}")
+            methods.debug_log('hikvision_detector', f"m-64: wait 10 minutes try again!")
+            time.sleep(600)
+            continue
+
+
+if __name__ == '__main__':
+    _args = ['python3', '192.168.0.181', 'admin', 'DEVdev123']
+    main(_args)

BIN
3rdparty/xapi/mccbts/1.txt


+ 339 - 0
3rdparty/xapi/mccbts/api.py

@@ -0,0 +1,339 @@
+# update: 2024.7.14
+"""
+中冶宝钢智慧研究院接口
+"""
+from urllib import parse
+import requests
+import time
+import traceback
+
+import sys
+import importlib
+
+# sys.path.append(r'C:\mccbts-ar-py\module-method-python')  # for 10.10.61.22
+sys.path.append(r'C:\mccbts-ar-py\module-method-python')  # for 10.10.61.22
+# sys.path.append(r'D:\mccbts-ar-py\module-method-python')  # for 192.168.103.34
+# methods = importlib.import_module(f"libraries.base_original")
+methods = importlib.import_module(f"xlib")
+
+
+class API(object):
+
+    # def __init__(self, username='System001', password='Swdx@143',
+    def __init__(self, username='', password=''):
+        """
+        接口地址: https://eaiops.mccbts.com.cn/cigpf-gateway/cigpf-auth/login
+        接口说明: 用户认证接口,mcctest/Mcc@1234
+
+        接口地址: http://ycpx04.mccbts.com.cn/cnki-gw/expert/expert/getExpertList
+        接口说明:获取专家列表
+
+        接口地址: http://10.8.0.151:8000/datachange/release/interface
+        接口说明:该接口可以获取用户数据、部门数据
+        """
+
+        # --- define ---
+        self.cigpf_api_service = f"https://eaiops.mccbts.com.cn"
+        self.cnki_api_service = f"http://ycpx04.mccbts.com.cn"
+        self.datachange_api_service = f"http://10.8.0.151:8000"
+
+        self.LIST_USER_TOKEN = 'FA460AD97660475BBD8E1CF31BC7A49B'
+        self.LIST_DEPT_TOKEN = '4412B9724D254E38B57014CF99EB0312'
+
+    def get_user_info(self, username, password):
+        """
+        获取用户信息
+        return {
+            'success': True,
+            'message': '操作成功!',
+            'code': 200,
+            'result': {
+                'id': '1399259233832144898',
+                'username': 'mcctest',
+                'realname': '测试账号',
+                'avatar': '/cigpfupload/05a5b1ab84a446c4a2e126da89b0c0af.png',
+                'birthday': None,
+                'sex': None,
+                'thirdId': '',
+                'thirdType': 'qt',
+                'email': None,
+                'phone': '18210635895',
+                'orgCode': '1',
+                'orgCodeTxt': None,
+                'status': 1,
+                'delFlag': 0,
+                'workNo': '99999',
+                'post': None,
+                'telephone': None,
+                'createBy': '',
+                'createTime': None,
+                'updateBy': '张宇',
+                'updateTime': '2023-11-07 06:00:09',
+                'activitiSync': 1,
+                'userIdentity': 2,
+                'departIds': '1000051C',
+                'relTenantIds': None,
+                'clientId': None,
+                'userType': '2'
+            },
+            'timestamp': 1699666081555
+        }
+        """
+        url = f"{self.cigpf_api_service}/cigpf-gateway/cigpf-auth/login"
+        # data = {
+        #     'userName': 'mcctest',  # 用户
+        #     'passWord': 'Mcc@1234',  # 密码
+        # }
+        data = {
+            'userName': username,  # 用户
+            'passWord': password,  # 密码
+        }
+        # print(f"API.get_user_info:data: {data}")
+        print('API.get_user_info.url:', url)
+        response = requests.post(url, json=data)
+
+        if response.status_code > 300:
+            print('API.get_user_info.result:', response.status_code)
+            print('API.get_user_info.detail:', response.text)
+            return {}
+        else:
+            print('API.get_user_info.result:', response.status_code)
+            return response.json()
+
+    def get_expert_type_tree_list(self, q=''):
+        """
+        获取专家结构树
+        """
+        url = f"https://ycpx04.mccbts.com.cn/cnki-gw/expert/expertType/getExpertTypeTreeList"
+        data = {
+            'keyword': q
+        }
+        response = requests.post(url, json=data)
+        # print('API.test.url:', url)
+        # print('API.test.data:', data)
+        # print('API.test.code:', response.status_code)
+        # print('API.test.text:', response.text)
+        return response.json()
+
+    def get_expert_list(self, page=1, rows=10):
+        """
+        获取专家列表
+        return {
+            'msg': '操作成功',
+            'code': 200,
+            'data': {
+                'total': 320,
+                'list': [
+                    {
+                        'id': 407880669122629,
+                        'expertId': 'b46f7f7e-2883-4281-ba92-0c72fb30a668',
+                        'expertCode': '40886',
+                        'expertName': '冯浩川',
+                        'expertLogo': '',
+                        'sex': 1,
+                        'birthday': '1974-08-12 00:00:00',
+                        'education': None,
+                        'degree': None,
+                        'colleges': '',
+                        'politicalOutlook': '',
+                        'academicTitle': '',
+                        'expertTypeId': 'a0846382-dc33-44f5-990d-ea042ab90cf4',
+                        'expertType': '工程机械组',
+                        'workUnitOrCurrentPosition': '',
+                        'professionalAndTechnicalFields': '',
+                        'mainSocialPositions': '',
+                        'personalHonors': '',
+                        'project': [],
+                        'brief': '',
+                        'createTime': '2023-04-17 15:30:43',
+                        'createUserId': '6867b1b3-024f-4065-b991-3ebd48df7b27',
+                        'type': '',
+                        'companyId': '45b9801f-d2ff-4886-8076-7f7d8b403e37',
+                        'company': '协力生产分公司',
+                        'departmentId': '301e6551-dfd6-4e3e-b7f5-be36e14f84a1',
+                        'department': '协力_机械维修中心_管理组'
+                    }
+                ]
+            }
+        }
+        """
+        url = f"{self.cnki_api_service}/cnki-gw/expert/expert/getExpertList"
+        data = {
+            'pageDto': {
+                'page': page,
+                'rows': rows
+            }
+        }
+        # print(f"API.get_user_info:data: {data}")
+        print('API.get_expert_list.url:', url)
+        response = requests.post(url, json=data)
+
+        # --- check ---
+        if response.status_code > 300:
+            print('API.get_expert_list.result:', response.status_code)
+            print('API.get_expert_list.detail:', response.text)
+            return []
+
+        # --- check ---
+        data = response.json()
+        if data.get('code') != 200:
+            return []
+
+        # --- check ---
+        if not data:
+            return []
+
+        # print(data.get('data').get('total'))
+        return data.get('data').get('list')
+
+    def get_staff_list(self):
+        """
+        获取员工列表
+        return [
+            {
+                'STATUS': '1',
+                'ORG_CODE': '1603605',
+                'UPDATE_BY': 'HR',
+                'CREATE_BY': 'HR',
+                'DEPART_NAME': '山西_天车二区_硅钢班',
+                'DEL_FLAG': '0',
+                'CREATE_TIME': 1659117651000,
+                'UPDATE_TIME': 1659117651000,
+                'ID': '1001G1AR',
+                'DEPART_NAME_ABBR': '山西_天车二区_硅钢班',
+                'C1': '1',
+                'PARENT_ID': '1001G1AM'
+            }
+        ]
+        """
+        url = f"{self.datachange_api_service}/datachange/release/interface"
+        headers = {'Chinasofti-Access-Token': self.LIST_USER_TOKEN}
+        print('API.get_staff_list.url:', url)
+        print('API.get_staff_list.headers:', headers)
+        response = requests.get(url, headers=headers)
+
+        # --- check ---
+        if response.status_code > 300:
+            print('API.get_staff_list.result:', response.status_code)
+            print('API.get_staff_list.detail:', response.text)
+            return '请求请求失败', f"code: {response.status_code}, response: {response.text}"
+
+        # --- check ---
+        data = response.json()
+        if data.get('code') != 0:
+            # print('API.get_staff_list.detail:', response.text)
+            return '接口返回异常', f"请求地址: {url}, 请求方式: GET, 请求参数: {headers}, 返回结果: {response.text}"
+
+        return data.get('data')
+
+    def get_department_list(self):
+        """
+        获取部门列表
+        return [
+            {
+                'ORG_ID': '10012B4J',
+                'REALNAME': '张天勤',
+                'POST': '电焊工',
+                'PHONE': '15921009690',
+                'SEX': 1,
+                'UPDATE_BY': 'HR',
+                'DEL_FLAG': 0,
+                'UPDATE_TIME': 1661492148000,
+                'WORK_NO2': '983260',
+                'STATUS': '1',
+                'PKID': 'C1924F31EE850472E050080A5C00BCBA',
+                'ORG_CODE': '153420410',
+                'CREATE_BY': 'HR',
+                'CREATE_TIME': 1620205014000,
+                'ID': '10000RBZ',
+                'WORK_NO': '983260',
+                'BIRTHDAY': '1967-02-09'
+            }
+        ]
+        """
+        url = f"{self.datachange_api_service}/datachange/release/interface"
+        headers = {'Chinasofti-Access-Token': self.LIST_DEPT_TOKEN}
+        print('API.get_department_list.url:', url)
+        print('API.get_department_list.headers:', headers)
+        response = requests.get(url, headers=headers)
+        print('API.get_department_list.status_code:', response.status_code)
+        print('API.get_department_list.text:', response.text)
+
+        # --- check ---
+        if response.status_code > 300:
+            # print('API.get_department_list.result:', response.status_code)
+            # print('API.get_department_list.detail:', response.text)
+            return {'code': -1, 'data': response.text}
+
+        # --- check ---
+        data = response.json()
+        if data.get('code') != 0:
+            # print('API.get_department_list.code:', data.get('code'))
+            # print('API.get_department_list.data:', response.text)
+            return {'code': -1, 'data': response.text}
+
+        return {'code': 0, 'data': data.get('data')}
+
+    def test(self):
+        """
+        """
+        url = f"http://ycpx04.mccbts.com.cn/cnki-gw/expert/expertType/getExpertTypeList"
+        data = {
+            'keyword': 'xxx'
+        }
+        response = requests.post(url, json=data)
+        print('API.test.url:', url)
+        print('API.test.data:', data)
+        print('API.test.code:', response.status_code)
+        print('API.test.text:', response.text)
+        # res = res.json()
+        # data = {
+        #     'count': len(res['data']),
+        #     'data': res['data']
+        # }
+
+
+if __name__ == '__main__':
+    # --- init ---
+    api = API()
+
+    # --- test ---
+    items = api.get_staff_list()
+    # items = api.get_department_list()
+    print(items)
+
+    # --- test ---
+    # items = api.get_expert_list(page=1, rows=100000000)
+    # for item in items:
+    #     if 'c0dbc699-1640-43c2-8459-ffadbaa2d966' not in item.get('expertTypeId'):
+    #         continue
+    #     print(item)
+
+    # --- test ---
+    # items = api.get_expert_type_tree_list()
+    # print(items)
+
+    # --- test --- 获取一级公司
+    # d1 = list()
+    # items = api.get_department_list().get('data')
+    # for item in items:
+    #     # if item.get('ORG_CODE') == '191':
+    #     #     print(item)
+    #     # if '总部' in item.get('DEPART_NAME'):
+    #     # if '总部' in item.get('DEPART_NAME'):
+    #     #     print(item)
+    #     if len(item.get('ORG_CODE')) == 3 and item.get('ORG_CODE')[0] == '1':
+    #     # if item.get('ORG_CODE') in ['3', '4', '6']:
+    #         # print(item)
+    #         # d1.append(item.get('DEPART_NAME'))
+    #         print(item.get('ORG_CODE'), item.get('DEPART_NAME'))
+    # print(d1, len(d1))
+
+    # --- test ---
+    # items = api.get_expert_type_tree_list().get('data')
+    # print(items[0]['children'])
+    # for i in items[0]['children']:
+    #     print(len(i.get('children')), i.get('count'))
+    #     if i.get('children'):
+    #         for j in i.get('children'):
+    #             print(len(j.get('children')), j.get('count'))

+ 564 - 0
3rdparty/xapi/nessus/api.py

@@ -0,0 +1,564 @@
+# update: 2021-6-28-19
+import requests
+import logging
+import time
+import copy
+import re
+
+requests.packages.urllib3.disable_warnings()
+TEMPLATES = ['PCI Quarterly External Scan', 'Host Discovery', 'WannaCry Ransomware', 'Intel AMT Security Bypass',
+             'Basic Network Scan', 'Credentialed Patch Audit', 'Web Application Tests', 'Malware Scan',
+             'Mobile Device Scan', 'MDM Config Audit', 'Policy Compliance Auditing', 'Internal PCI Network Scan',
+             'Offline Config Audit', 'Audit Cloud Infrastructure', 'SCAP and OVAL Auditing', 'Custom Scan',
+             'Bash Shellshock Detection', 'GHOST (glibc) Detection', 'DROWN Detection', 'Badlock Detection',
+             'Shadow Brokers Scan', 'Spectre and Meltdown', 'Advanced Scan', 'Advanced Dynamic Scan']
+DefaultPolicyForAdvancedScan = {
+    "uuid": "ad629e16-03b6-8c1d-cef6-ef8c9dd3c658d24bd260ef5f9e66",
+    "plugins": {
+        "SMTP problems": {"status": "enabled"},
+        "Backdoors": {"status": "enabled"},
+        "Ubuntu Local Security Checks": {"status": "enabled"},
+        "Gentoo Local Security Checks": {"status": "enabled"},
+        "Oracle Linux Local Security Checks": {"status": "enabled"},
+        "RPC": {"status": "enabled"},
+        "Gain a shell remotely": {"status": "enabled"},
+        "Service detection": {"status": "enabled"},
+        "DNS": {"status": "enabled"},
+        "Mandriva Local Security Checks": {"status": "enabled"},
+        "Junos Local Security Checks": {"status": "enabled"},
+        "Misc.": {"status": "enabled"},
+        "FTP": {"status": "enabled"},
+        "Slackware Local Security Checks": {"status": "enabled"},
+        "Default Unix Accounts": {"status": "enabled"},
+        "AIX Local Security Checks": {"status": "enabled"},
+        "SNMP": {"status": "enabled"},
+        "OracleVM Local Security Checks": {"status": "enabled"},
+        "CGI abuses": {"status": "enabled"},
+        "Settings": {"status": "enabled"},
+        "CISCO": {"status": "enabled"},
+        "Firewalls": {"status": "enabled"},
+        "Databases": {"status": "enabled"},
+        "Debian Local Security Checks": {"status": "enabled"},
+        "Fedora Local Security Checks": {"status": "enabled"},
+        "Netware": {"status": "enabled"},
+        "Huawei Local Security Checks": {"status": "enabled"},
+        "Windows : User management": {"status": "enabled"},
+        "VMware ESX Local Security Checks": {"status": "enabled"},
+        "Virtuozzo Local Security Checks": {"status": "enabled"},
+        "CentOS Local Security Checks": {"status": "enabled"},
+        "Peer-To-Peer File Sharing": {"status": "enabled"},
+        "NewStart CGSL Local Security Checks": {"status": "enabled"},
+        "General": {"status": "enabled"},
+        "Policy Compliance": {"status": "enabled"},
+        "Amazon Linux Local Security Checks": {"status": "enabled"},
+        "Solaris Local Security Checks": {"status": "enabled"},
+        "F5 Networks Local Security Checks": {"status": "enabled"},
+        "Denial of Service": {"status": "enabled"},
+        "Windows : Microsoft Bulletins": {"status": "enabled"},
+        "SuSE Local Security Checks": {"status": "enabled"},
+        "Palo Alto Local Security Checks": {"status": "enabled"},
+        "Red Hat Local Security Checks": {"status": "enabled"},
+        "PhotonOS Local Security Checks": {"status": "enabled"},
+        "HP-UX Local Security Checks": {"status": "enabled"},
+        "CGI abuses : XSS": {"status": "enabled"},
+        "FreeBSD Local Security Checks": {"status": "enabled"},
+        "Windows": {"status": "enabled"},
+        "Scientific Linux Local Security Checks": {"status": "enabled"},
+        "MacOS X Local Security Checks": {"status": "enabled"},
+        "Web Servers": {"status": "enabled"},
+        "SCADA": {"status": "enabled"}
+    },
+    "credentials": {"add": {}, "edit": {}, "delete": []},
+    "settings": {
+        "patch_audit_over_telnet": "no", "patch_audit_over_rsh": "no", "patch_audit_over_rexec": "no",
+        "snmp_port": "161", "additional_snmp_port1": "161", "additional_snmp_port2": "161",
+        "additional_snmp_port3": "161", "http_login_method": "POST", "http_reauth_delay": "",
+        "http_login_max_redir": "0", "http_login_invert_auth_regex": "no", "http_login_auth_regex_on_headers": "no",
+        "http_login_auth_regex_nocase": "no", "never_send_win_creds_in_the_clear": "yes",
+        "dont_use_ntlmv1": "yes", "start_remote_registry": "no", "enable_admin_shares": "no", "ssh_known_hosts": "",
+        "ssh_port": "22", "ssh_client_banner": "OpenSSH_5.0", "attempt_least_privilege": "no",
+        "region_dfw_pref_name": "yes", "region_ord_pref_name": "yes", "region_iad_pref_name": "yes",
+        "region_lon_pref_name": "yes", "region_syd_pref_name": "yes", "region_hkg_pref_name": "yes",
+        "microsoft_azure_subscriptions_ids": "", "aws_ui_region_type": "Rest of the World",
+        "aws_us_east_1": "", "aws_us_east_2": "", "aws_us_west_1": "", "aws_us_west_2": "",
+        "aws_ca_central_1": "", "aws_eu_west_1": "", "aws_eu_west_2": "", "aws_eu_west_3": "",
+        "aws_eu_central_1": "", "aws_eu_north_1": "", "aws_ap_east_1": "", "aws_ap_northeast_1": "",
+        "aws_ap_northeast_2": "", "aws_ap_northeast_3": "", "aws_ap_southeast_1": "", "aws_ap_southeast_2": "",
+        "aws_ap_south_1": "", "aws_me_south_1": "", "aws_sa_east_1": "", "aws_use_https": "yes",
+        "aws_verify_ssl": "yes", "log_whole_attack": "no", "enable_plugin_debugging": "no",
+        "audit_trail": "use_scanner_default", "include_kb": "use_scanner_default", "enable_plugin_list": "no",
+        "custom_find_filepath_exclusions": "", "custom_find_filesystem_exclusions": "",
+        "reduce_connections_on_congestion": "no", "network_receive_timeout": "5", "max_checks_per_host": "5",
+        "max_hosts_per_scan": "100", "max_simult_tcp_sessions_per_host": "", "max_simult_tcp_sessions_per_scan": "",
+        "safe_checks": "yes", "stop_scan_on_disconnect": "no", "slice_network_addresses": "no",
+        "allow_post_scan_editing": "yes", "reverse_lookup": "no", "log_live_hosts": "no",
+        "display_unreachable_hosts": "no", "report_verbosity": "Normal", "report_superseded_patches": "yes",
+        "silent_dependencies": "yes", "scan_malware": "no", "samr_enumeration": "yes", "adsi_query": "yes",
+        "wmi_query": "yes", "rid_brute_forcing": "no", "request_windows_domain_info": "no",
+        "scan_webapps": "no", "test_default_oracle_accounts": "no", "provided_creds_only": "yes",
+        "smtp_domain": "example.com", "smtp_from": "nobody@example.com", "smtp_to": "postmaster@[AUTO_REPLACED_IP]",
+        "av_grace_period": "0", "report_paranoia": "Normal", "thorough_tests": "no",
+        "svc_detection_on_all_ports": "yes", "detect_ssl": "yes", "ssl_prob_ports": "Known SSL ports",
+        "cert_expiry_warning_days": "60", "enumerate_all_ciphers": "yes", "check_crl": "no",
+        "tcp_scanner": "no", "tcp_firewall_detection": "Automatic (normal)", "syn_scanner": "yes",
+        "syn_firewall_detection": "Automatic (normal)", "udp_scanner": "no", "ssh_netstat_scanner": "yes",
+        "wmi_netstat_scanner": "yes", "snmp_scanner": "yes", "only_portscan_if_enum_failed": "yes",
+        "verify_open_ports": "no", "unscanned_closed": "no", "portscan_range": "default",
+        "wol_mac_addresses": "", "wol_wait_time": "5", "scan_network_printers": "no", "scan_netware_hosts": "no",
+        "scan_ot_devices": "no", "ping_the_remote_host": "yes", "arp_ping": "yes", "tcp_ping": "yes",
+        "tcp_ping_dest_ports": "built-in", "icmp_ping": "yes", "icmp_unreach_means_host_down": "no",
+        "icmp_ping_retries": "2", "udp_ping": "no", "test_local_nessus_host": "yes",
+        "fast_network_discovery": "no", "name": "test-1122", "description": ""
+    }
+}
+
+
+class Api(object):
+
+    def __init__(self, service_url='https://47.104.160.37:8001', username='admin', password='admin'):
+        self.debugs = str()
+        self.errors = str()
+        self.urls = list()
+        self.service_url = service_url
+        self.headers = dict()
+        self.headers['content-type'] = 'application/json'
+        self.headers['X-API-Token'] = self.get_api_token()
+        self.create_super_user(username, password)
+        token = self.get_session(username, password)
+        self.headers['X-Cookie'] = f'token={token}'
+
+    def debug(self, **kwargs):
+        for k, v in kwargs.items():
+            self.debugs += str(k) + ':' + str(v) + ';'
+
+    def error(self, **kwargs):
+        for k, v in kwargs.items():
+            self.errors += str(k) + ':' + str(v) + ';'
+
+    def get_policy_id_by_name(self, name):
+        """根据策略名称获取策略id"""
+        for policy in self.get_policies():
+            if policy['name'] != name:
+                continue
+            return policy['id']
+        return ''
+
+    def get_template_id_by_title(self, title):
+        """根据策略类型标题获取策略类型id"""
+        if title not in TEMPLATES:
+            return ''
+        for template in self.get_templates():
+            if template['title'] != title:
+                continue
+            return template['uuid']
+        return ''
+
+    def scan_is_completed(self, scan_id):
+        """检查执行状态"""
+        return self.get_scan_status(scan_id) == 'completed'
+
+    def create_super_user(self, username, password):
+        """创建超级管理员用户"""
+        # --- 是否为register ---
+        if self.server_status() != 'register':
+            return
+        self.create_user(username, password)
+        self.server_restart()
+        # --- 等待loading结束 ---
+        while True:
+            try:
+                if self.server_status() == 'ready':
+                    break
+                print(self.server_status())
+                time.sleep(5)
+            except Exception as e:
+                print(e.__class__.__name__)
+                time.sleep(5)
+
+    def create_user(self, username, password):
+        """
+        url: https://<ip地址>:8834/users
+        """
+        if 'register' != self.server_status():
+            return 0
+
+        url = f'{self.service_url}/users'
+        data = {
+            'username': username,
+            'password': password,
+            'permissions': 128,
+        }
+
+        print('NessusApiLog:create_user:url:', url)
+        response = requests.post(url=url, json=data, headers=self.headers, verify=False)
+        print('NessusApiLog:create_user:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='create_user', result=response.text)
+            return 0
+        else:
+            self.urls.append(url)
+            self.debug(method='create_user', result=response.text)
+            return response.json()['id']
+
+    def server_restart(self):
+        """
+        url: https://<ip地址>:8834/server/restart
+        """
+        url = f'{self.service_url}/server/restart'
+
+        print('NessusApiLog:server_restart:url:', url)
+        response = requests.post(url=url, headers=self.headers, verify=False)
+        print('NessusApiLog:server_restart:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='server_restart', result=response.text)
+            return False
+        else:
+            self.debug(method='server_restart', result=response.text)
+            return True
+
+    def server_status(self):
+        """
+        url: https://118.190.217.96:8001/server/status
+        status:
+            loading
+            register
+            ready
+        """
+        url = f'{self.service_url}/server/status'
+
+        print('NessusApiLog:server_status:url:', url)
+        response = requests.get(url=url, headers=self.headers, verify=False)
+        print('NessusApiLog:server_status:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='restart', result=response.text)
+            return ''
+        else:
+            self.debug(method='restart', result=response.text)
+            return response.json()['status']
+
+    def get_session(self, username, password):
+        """
+        url: http://<ip地址>:8834/session
+        """
+        url = f'{self.service_url}/session'
+        data = {
+            'username': username,
+            'password': password,
+        }
+        print('NessusApiLog:get_token:url:', url)
+        response = requests.post(url=url, json=data, headers=self.headers, verify=False)
+        print('NessusApiLog:get_token:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_token', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_token', result=response.text)
+            return response.json()['token']
+
+    def get_keys(self):
+        """
+        url: http://<ip地址>:8834/session/keys
+        doc: https://47.104.160.37:8001/api#/resources/session/keys
+        """
+        url = f'{self.service_url}/session/keys'
+
+        print('NessusApiLog:get_keys:url:', url)
+        response = requests.put(url=url, headers={'content-type': 'application/json'}, verify=False)
+        print('NessusApiLog:get_keys:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_keys', result=response.text)
+            return {}
+        else:
+            self.debug(method='get_keys', result=response.text)
+            return response.json()
+
+    def get_policy(self, policy_id):
+        """
+        url: http://<ip地址>:8834/policies/<policy_id>
+        """
+        url = f'{self.service_url}/policies/{policy_id}'
+        print('NessusApiLog:get_policy:url:', url)
+        response = requests.get(url, headers=self.headers, verify=False)
+        print('NessusApiLog:get_policy:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_policy', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_policy', result=response.text)
+            return response.json()
+
+    def get_policies(self):
+        """
+        url: http://<ip地址>:8834/policies
+        """
+        url = f'{self.service_url}/policies'
+        print('NessusApiLog:get_policies:url:', url)
+        response = requests.get(url, headers=self.headers, verify=False, timeout=300)
+        print('NessusApiLog:get_policies:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_policies', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_policies', result=response.text)
+            return response.json()['policies']
+
+    def get_scan_status(self, scan_id):
+        """
+        url: http://<ip地址>:8834/scans/<scan_id>
+        """
+        url = f'{self.service_url}/scans/{scan_id}'
+        params = {'includeHostDetailsForHostDiscovery': 'true', 'limit': 2500}
+
+        print('NessusApiLog:get_scan:url:', url)
+        response = requests.get(url, headers=self.headers, params=params, verify=False)
+        print('NessusApiLog:get_scan:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_scan', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_scan', result=response.text)
+            return response.json()['info']['status']
+
+    def get_scan(self, scan_id):
+        """
+        url: http://<ip地址>:8834/scans/<scan_id>
+        """
+        url = f'{self.service_url}/scans/{scan_id}'
+        params = {'includeHostDetailsForHostDiscovery': 'true', 'limit': 2500}
+
+        print('NessusApiLog:get_scan:url:', url)
+        response = requests.get(url, headers=self.headers, params=params, verify=False)
+        print('NessusApiLog:get_scan:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_scan', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_scan', result=response.text)
+            return response.json()['hosts']
+
+    def get_scans(self):
+        """
+        url: http://<ip地址>:8834/scans
+        """
+        url = f'{self.service_url}/scans'
+
+        print('NessusApiLog:get_scans:url:', url)
+        response = requests.get(url, headers=self.headers, verify=False)
+        print('NessusApiLog:get_scans:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_scans', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_scans', result=response.text)
+            return response.json()['scans']
+
+    def create_scan(self, template_uuid, scan_name, policy_id, targets):
+        """
+        url: http://<ip地址>:8834/scans
+        doc: https://192.168.20.162:8834/api#/resources/scans/create
+        """
+        url = f'{self.service_url}/scans'
+        data = {
+            "uuid": template_uuid,
+            "settings": {
+                "emails": "",
+                "filter_type": "and",
+                "filters": [],
+                "launch_now": True,
+                "enabled": False,
+                "name": scan_name,
+                "description": "",
+                # "folder_id": 3,
+                # "scanner_id": "1",
+                "policy_id": str(policy_id),
+                "text_targets": targets,
+                "file_targets": ""
+            }
+        }
+
+        print('NessusApiLog:create_scan:url:', url)
+        response = requests.post(url, json=data, headers=self.headers, verify=False)
+        print('NessusApiLog:create_scan:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='create_scan', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='create_scan', result=response.text)
+            return response.json()['scan']['id']
+
+    def delete_scan(self, scan_id):
+        """
+        url: http://<ip地址>:8834/scans/<scan_id>
+        """
+        url = f'{self.service_url}/scans/{scan_id}'
+
+        print('NessusApiLog:delete_scan:url:', url)
+        response = requests.delete(url, headers=self.headers, verify=False)
+        print('NessusApiLog:delete_scan:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='delete_scan', result=response.text)
+            return False
+        else:
+            self.urls.append(url)
+            self.debug(method='delete_scan', result=response.text)
+            return True
+
+    def start_scan(self, scan_id):
+        """
+        url: http://<ip地址>:8834/scans/<scan_id>/launch
+        """
+        url = f'{self.service_url}/scans/{scan_id}/launch'
+
+        print('NessusApiLog:start_scan:url:', url)
+        response = requests.post(url, json={}, headers=self.headers, verify=False)
+        print('NessusApiLog:start_scan:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='start_scan', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='start_scan', result=response.text)
+            return response.json()
+
+    def get_host(self, scan_id, host_id):
+        """
+        url: https://192.168.20.162:8834/scans/68/hosts/232
+        doc: https://192.168.20.162:8834/api#/resources/scans/host-details
+        """
+        url = f'{self.service_url}/scans/{scan_id}/hosts/{host_id}'
+
+        print('NessusApiLog:get_host:url:', url)
+        response = requests.get(url, params={}, headers=self.headers, verify=False)
+        print('NessusApiLog:get_host:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_host', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_host', result=response.text)
+            return response.json()
+
+    def get_plugin(self, scan_id, host_id, plugin_id):
+        """
+        url: https://192.168.20.162:8834/scans/68/hosts/232/plugins/100464
+        """
+        url = f'{self.service_url}/scans/{scan_id}/hosts/{host_id}/plugins/{plugin_id}'
+
+        print('NessusApiLog:get_plugin:url:', url)
+        response = requests.get(url, params={}, headers=self.headers, verify=False)
+        print('NessusApiLog:get_plugin:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_plugin', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_plugin', result=response.text)
+            return response.json()
+
+    def get_plugin_info(self, plugin_id):
+        """
+        url: https://192.168.20.162:8834/plugins/plugin/{id}
+        doc: https://192.168.20.162:8834/api#/resources/plugins/plugin-details
+        """
+        url = f'{self.service_url}/plugins/plugin/{plugin_id}'
+
+        print('NessusApiLog:get_plugin_info:url:', url)
+        response = requests.get(url, params={}, headers=self.headers, verify=False)
+        print('NessusApiLog:get_plugin_info:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_plugin_info', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_plugin_info', result=response.text)
+            return response.json()
+
+    def get_templates(self, template_type='policy'):
+        """
+        url: https://172.30.2.8:8001/editor/{type}/templates
+        doc: https://172.30.2.8:8001/api#/resources/editor/list
+        """
+        url = f'{self.service_url}/editor/{template_type}/templates'
+
+        print('NessusApiLog:get_templates:url:', url)
+        response = requests.get(url, params={}, headers=self.headers, verify=False)
+        print('NessusApiLog:get_templates:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='get_templates', result=response.text)
+        else:
+            self.urls.append(url)
+            self.debug(method='get_templates', result=response.text)
+            return response.json()['templates']
+
+    def create_policy(self, template_uuid, policy_name, settings=None):
+        """
+        url: https://192.168.20.162:8834/policies
+        doc: https://192.168.20.162:8834/api#/resources/policies/create
+        """
+        url = f'{self.service_url}/policies/'
+
+        data = copy.copy(DefaultPolicyForAdvancedScan)
+        data['uuid'] = template_uuid
+        data['settings']['name'] = policy_name
+        if settings:
+            data['settings'].update(settings)
+
+        print('NessusApiLog:create_policy:url:', url)
+        response = requests.post(url, json=data, headers=self.headers, verify=False)
+        print('NessusApiLog:create_policy:result:', response.status_code)
+
+        if response.status_code > 300:
+            self.error(method='create_policy', result=response.text)
+            return ''
+        else:
+            self.urls.append(url)
+            self.debug(method='create_policy', result=response.text)
+            return response.json()['policy_id']
+
+    def get_api_token(self):
+        """
+        url: https://192.168.20.162:8844/nessus6.js?v=1583846805284
+        """
+        url = f'{self.service_url}/nessus6.js'
+        params = {'v': 1583846805284}
+
+        print('NessusApiLog:get_api_token:url:', url)
+        response = requests.get(url=url, params=params, headers=self.headers, verify=False)
+        print('NessusApiLog:get_api_token:result:', response.status_code)
+
+        if response.status_code > 300:
+            # self.error(method='get_api_token', result=response.text)
+            return ''
+        else:
+            self.urls.append(url)
+            # self.debug(method='get_api_token', result=response.text)
+            pattern = r'return"([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})'
+            return re.findall(pattern, response.text)[0]
+
+
+if __name__ == '__main__':
+    # api = Api(service_url='https://118.190.217.96:8001', username='admin', password='admin')
+    # api = Api(service_url='https://47.104.224.202:8001', username='admin', password='admin')
+    api = Api(service_url='https://172.30.2.8:8001', username='admin', password='admin')
+
+    # --- test api ---
+    # print(api.server_status())
+    # print(api.server_restart())
+    # print(api.server_status())
+    print(api.get_policies())
+    print(api.errors)
+    # print(api.get_templates())
+    # print(api.get_template_id_by_title('Advanced Scan'))

+ 124 - 0
3rdparty/xapi/onvif/api.py

@@ -0,0 +1,124 @@
+# note: https://github.com/FalkTannhaeuser/python-onvif-zeep
+# note: https://blog.csdn.net/zong596568821xp/article/details/89644654
+from onvif import ONVIFCamera
+import zeep
+import time
+
+mycam = ONVIFCamera('192.168.30.130', 80, 'admin', '123456',
+                    '/home/packages/test/test_onvif/python-onvif-zeep-0.2.12/wsdl/')
+
+# Create media service object
+media = mycam.create_media_service()
+# Create ptz service object
+ptz = mycam.create_ptz_service()
+
+
+# Get target profile
+def zeep_pythonvalue(self, xmlvalue):
+    return xmlvalue
+
+
+zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue
+media_profile = media.GetProfiles()[0]
+
+# Get PTZ configuration options for getting continuous move range
+request = ptz.create_type('GetConfigurationOptions')
+request.ConfigurationToken = media_profile.PTZConfiguration.token
+ptz_configuration_options = ptz.GetConfigurationOptions(request)
+request = ptz.create_type('ContinuousMove')
+request.ProfileToken = media_profile.token
+ptz.Stop({'ProfileToken': media_profile.token})
+
+if request.Velocity is None:
+    request.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position
+    request.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position
+    request.Velocity.PanTilt.space = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].URI
+    request.Velocity.Zoom.space = ptz_configuration_options.Spaces.ContinuousZoomVelocitySpace[0].URI
+
+XMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max
+XMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Min
+YMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max
+YMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min
+
+
+def move_right(_ptz, _request):
+    """向右持续移动n秒"""
+    print('move right...')
+    _request.Velocity.PanTilt.x = XMAX
+    _request.Velocity.PanTilt.y = 0
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(1)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+def move_left(_ptz, _request):
+    """向左持续移动n秒"""
+    print('move left...')
+    _request.Velocity.PanTilt.x = XMIN
+    _request.Velocity.PanTilt.y = 0
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(0.5)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+def move_up(_ptz, _request):
+    """向上持续移动n秒"""
+    print('move up...')
+    request.Velocity.PanTilt.x = 0
+    request.Velocity.PanTilt.y = YMAX
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(0.5)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+def move_down(_ptz, _request):
+    """向下持续移动n秒"""
+    print('move down...')
+    request.Velocity.PanTilt.x = 0
+    request.Velocity.PanTilt.y = YMIN
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(0.5)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+# move_right(ptz, request)
+# move_left(ptz, request)
+# move_up(ptz, request)
+# move_down(ptz, request)
+
+def zoom_in(_ptz, _request):
+    print('zoom in...')
+    _request.Velocity.Zoom.x = 1
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(1)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+def zoom_out(_ptz, _request):
+    print('zoom out...')
+    _request.Velocity.Zoom.x = -1
+    # Start continuous move
+    _ptz.ContinuousMove(_request)
+    # Wait a certain time
+    time.sleep(1)
+    # Stop continuous move
+    _ptz.Stop({'ProfileToken': _request.ProfileToken})
+
+
+zoom_in(ptz, request)
+# zoom_out(ptz, request)

+ 263 - 0
3rdparty/xapi/ros1/api_for_hs.py

@@ -0,0 +1,263 @@
+# update: 2024-3-20
+"""
+车辆控制器API
+"""
+from urllib import parse
+import requests
+import time
+import traceback
+
+import sys
+import importlib
+
+sys.path.append(r'E:\casper\repositories\repositories\casperz.py-project\module-py')  # for pc
+# sys.path.append(r'D:\casper\gitee\casperz.py-project\module-py')  # for pc
+methods = importlib.import_module(f"xlib")
+
+
+class API(object):
+
+    def __init__(self, address='192.168.191.91', port=8000):
+        """
+        start_plan 启动任务
+        abort_plan 终止任务
+        start_engine 远程打火
+        stall_engine 远程熄火
+        apply_use_by_ip 申请远程操作模式 1 申请远程操作模式 2 申请自动作业模式
+        """
+        # --- define ---
+        self.port = port
+        # self.url = f"http://{address}:{port}"
+
+    def cmd1001(self, address='192.168.191.91', navigation=[]):
+        """
+        启动行驶
+        data = {
+            # 指令类型
+            "cmdid": 1001,  # 执行行驶任务
+            # 指令参数
+            "param": {
+                # 导航数据
+                "navigation": [
+                    {
+                        'type': 'forward',  # 方向类型 forward 正向 backward 反向
+                        'nav_coordinates': [
+                            # 直行轨迹坐标参数
+                            {
+                                "start_point_x": 37.7749,
+                                "start_point_y": 37.7749,
+                                "end_point_x": 40,
+                                "end_point_y": -120
+                            },
+                            # 转弯轨迹坐标参数
+                            {
+                                "start_point_x": 40.7128,
+                                "start_point_y": -74.0060,
+                                "center_point_x": 10,
+                                "center_point_y": 20,
+                                "end_point_x": 50.7128,
+                                "end_point_y": -70.0060
+                            },
+                        ]
+                    },
+                    {
+                        'type': 'backward',
+                        'nav_coordinates': [
+                            # 直行轨迹坐标参数
+                            {
+                                "start_point_x": 37.7749,
+                                "start_point_y": -122.4194,
+                                "end_point_x": 40,
+                                "end_point_y": -120
+                            },
+                            # 转弯轨迹坐标参数
+                            {
+                                "start_point_x": 40.7128,
+                                "start_point_y": -74.0060,
+                                "center_point_x": 10,
+                                "center_point_y": 20,
+                                "end_point_x": 50.7128,
+                                "end_point_y": -70.0060
+                            },
+                        ]
+                    },
+                ],
+            },
+        }
+        """
+        data = {
+            'cmdid': 1001,
+            'param': {
+                'navigation': navigation,
+            }
+        }
+        url = f"http://{address}:{self.port}"
+        response = requests.post(self.url, json=data)
+        print('API.start_plan.url:', self.url)
+        print('API.start_plan.data:', data)
+        print('API.start_plan.code:', response.status_code)
+        print('API.start_plan.text:', response.text)
+
+    def cmd100101(self):
+        """
+        启动作业
+        """
+        data = {
+            # 指令类型
+            "cmdid": 100101,  # 启动作业
+            # 指令参数
+            "param": {
+                # 任务类型 102 叉包 103 放包 104 倒渣
+                "task_type": 102,
+                # 102
+                "task_coordinates": {
+                    "pot_name": "M.24",
+                    "pot_x": 40,
+                    "pot_y": 50,
+                    "mark_pot_pose": 0.3,
+                    "e_point_x": 50,
+                    "e_point_y": 60
+                },
+                # 103
+                # "task_coordinates": {
+                #     "pot_name": "M.24",
+                #     "pot_x": 40,
+                #     "pot_y": 50,
+                #     "goal_pot_pose": 0.3,
+                #     "e_point_x": 50,
+                #     "e_point_y": 60
+                # },
+                # 104
+                # "task_coordinates": {},
+            }
+        }
+
+    def cmd1002(self):
+        """
+        中止行驶任务
+        """
+        data = {
+            # 指令类型
+            "cmdid": 1002,  # 中止行驶任务
+            # 指令参数
+            "param": {}
+        }
+
+    def cmd1003(self):
+        data = {
+            # 指令类型
+            "cmdid": 1003,  # 远程打火
+            # 指令参数
+            "param": {}
+        }
+
+    def cmd1004(self):
+        data = {
+            # 指令类型
+            "cmdid": 1004,  # 远程熄火
+            # 指令参数
+            "param": {}
+        }
+
+    def cmd1005(self):
+        data = {
+            # 指令类型
+            "cmdid": 1005,  # 申请远程操作模式
+            # 指令参数
+            "param": {}
+        }
+
+    def cmd1006(self):
+        data = {
+            # 指令类型
+            "cmdid": 1006,  # 申请自动作业模式
+            # 指令参数
+            "param": {}
+        }
+
+    def test002(self, address):
+        """
+        """
+        data = {
+            # 指令类型
+            "cmdid": 1001,  # 执行行驶任务
+            # 指令参数
+            "param": {
+                # 导航数据
+                "navigation": [
+                    {
+                        'type': 'forward',
+                        'nav_coordinates': [
+                            {
+                                'start_point_x': 2.495,  # 当前位置
+                                'start_point_y': 173.1666142856,  # 当前位置
+                                'end_point_x': 182.6588877142857,
+                                'end_point_y': 169.8418064285714
+                            },
+                            {
+                                'start_point_x': 182.6588877142857,
+                                'start_point_y': 169.8418064285714,
+                                'center_point_x': 182.92185485714285,
+                                'center_point_y': 183.5769582142857,
+                                'end_point_x': 196.65700664285714,
+                                'end_point_y': 183.31399107142855
+                            },
+                            {
+                                'start_point_x': 196.65700664285714,
+                                'start_point_y': 183.31399107142855,
+                                'center_point_x': 182.92185485714285,
+                                'center_point_y': 183.5769582142857,
+                                'end_point_x': 183.184822,
+                                'end_point_y': 197.31211
+                            },
+                            {
+                                'start_point_x': 183.184822,
+                                'start_point_y': 197.31211,
+                                'end_point_x': 178.045984 - 50,
+                                'end_point_y': 197.219726
+                            }
+                        ]
+                    }
+                ]
+            },
+        }
+        url = f"http://{address}:{self.port}"
+        response = requests.post(url, json=data)
+        print('API.start_plan.url:', url)
+        print('API.start_plan.data:', data)
+        print('API.start_plan.code:', response.status_code)
+
+    def test004(self, address):
+        data = {
+            # 指令类型
+            "cmdid": 100101,  # 启动作业
+            # 指令参数
+            "param": {
+
+                "task_type": 103,  # 放包
+                "task_coordinates": {
+                    "pot_name": "N.20",
+                    "pot_x": 122.755687,
+                    "pot_y": 210.019149,
+                    "mark_pot_pose": -1.570920,
+                    "e_point_x": 0,
+                    "e_point_y": 0
+                },
+
+            }
+        }
+        url = f"http://{address}:{self.port}"
+        response = requests.post(url, json=data)
+        print('API.start_plan.url:', url)
+        print('API.start_plan.data:', data)
+        print('API.start_plan.code:', response.status_code)
+
+
+if __name__ == '__main__':
+    # --- init ---
+    api = API(port=8000)
+
+    # --- test ---
+    # out = api.test002(address='192.168.131.180')  # 行驶
+    out = api.test004(address='192.168.131.180')  # 作业
+    print(out)

+ 52 - 0
3rdparty/xapi/ros1/cmd1001.txt

@@ -0,0 +1,52 @@
+data = {
+    # 指令类型
+    "cmdid": 1001,  # 执行行驶任务
+    # 指令参数
+    "param": {
+        # 导航数据
+        "navigation": [
+            {
+                'type': 'forward',  # 方向类型 forward 正向 backward 反向
+                'nav_coordinates': [
+                    # 直行轨迹坐标参数
+                    {
+                        "start_point_x": 37.7749,
+                        "start_point_x": 37.7749,
+                        "end_point_x": 40,
+                        "end_point_y": -120
+                    },
+                    # 转弯轨迹坐标参数
+                    {
+                        "start_point_x": 40.7128,
+                        "start_point_y": -74.0060,
+                        "center_point_x": 10,
+                        "center_point_y": 20,
+                        "end_point_x": 50.7128,
+                        "end_point_y": -70.0060
+                    },
+                ]
+            },
+            {
+                'type': 'backward',
+                'nav_coordinates': [
+                    # 直行轨迹坐标参数
+                    {
+                        "start_point_x": 37.7749,
+                        "start_point_y": 37.7749,
+                        "end_point_x": 40,
+                        "end_point_y": -120
+                    },
+                    # 转弯轨迹坐标参数
+                    {
+                        "start_point_x": 40.7128,
+                        "start_point_y": -74.0060,
+                        "center_point_x": 10,
+                        "center_point_y": 20,
+                        "end_point_x": 50.7128,
+                        "end_point_y": -70.0060
+                    },
+                ]
+            },
+        ],
+    },
+}

+ 40 - 0
3rdparty/xapi/ros1/cmd100101.txt

@@ -0,0 +1,40 @@
+data = {
+    # 指令类型
+    "cmdid": 100101,  # 启动作业
+    # 指令参数
+    "param": {
+        # 任务类型 102 叉包 105 回e点 104 倒渣 103 放包 
+
+        # 102 叉包
+        # "task_type": 102,
+        # "task_coordinates": {
+        #     "pot_name": "N.12",
+        #     "pot_x": 78.381871,
+        #     "pot_y": 209.724510,
+        #     "mark_pot_pose": -1.559925,
+        #     "e_point_x": 50,
+        #     "e_point_y": 60
+        # },
+        # 105 回e点 (就是x轴的偏移量)
+        "task_type": 105,
+        "task_coordinates": {
+            "e_point_x": 78.381871 - 40,
+            "e_point_y": 199.66660199999998
+        },
+        # 104 倒渣
+        # "task_type": 104,
+        # "task_coordinates": {
+        #
+        # },
+        # 103 放包
+        # "task_type": 103,
+        # "task_coordinates": {
+        #     "pot_name": "N30",
+        #     "pot_x": 178.045984,
+        #     "pot_y": 209.219726,
+        #     "mark_pot_pose": -1.608619,
+        #     "e_point_x": 50,
+        #     "e_point_y": 60
+        # },
+    }
+}

+ 52 - 0
3rdparty/xapi/ros1/s1-行驶-去e点

@@ -0,0 +1,52 @@
+车开到n2位置,车头朝向倒渣口(事先需要联系师傅,将n20的包对上,我们需要记录一下包的位置信息)
+
+凯强 122.755687      210.019149     -1.570920
+
+n.20 122.8439634 198.54541533333332
+
+mqtt当前位置: 'position_x': 24.480705865127597, 'position_y': 202.35537350570837
+
+
+
+                "navigation": [
+                    {
+                        'type': 'forward',
+                        'nav_coordinates': [
+                            {
+                                'start_point_x': 24.4807,  # 当前n2位置
+                                'start_point_y': 202.35537350570837,  # 当前n2位置
+                                'end_point_x': 18.618844,
+                                'end_point_y': 200.67567
+                            },
+                            {
+                                'start_point_x': 18.618844,
+                                'start_point_y': 200.67567,
+                                'center_point_x': 18.306224428571426,
+                                'center_point_y': 186.9142662857143,
+                                'end_point_x': 17.993604857142852,
+                                'end_point_y': 173.15286257142859
+                            },
+                            {
+                                'start_point_x': 17.993604857142852,
+                                'start_point_y': 173.15286257142859,
+                                'end_point_x': 182.6588877142857,
+                                'end_point_y': 169.8418064285714
+                            },
+                            {
+                                'start_point_x': 182.6588877142857,
+                                'start_point_y': 169.8418064285714,
+                                'center_point_x': 182.92185485714285,
+                                'center_point_y': 183.5769582142857,
+                                'end_point_x': 183.184822,
+                                'end_point_y': 197.31211
+                            },
+                            {
+                                'start_point_x': 183.184822,
+                                'start_point_y': 197.31211,
+                                # e点位置
+                                'end_point_x': 122.8439634 - 50,  # e点需要往前开50
+                                'end_point_y': 198.54541533333332
+                            }
+                        ]
+                    }
+                ]

+ 12 - 0
3rdparty/xapi/ros1/s2-作业-叉包

@@ -0,0 +1,12 @@
+凯强 122.755687      210.019149     -1.570920
+
+
+                "task_type": 102,  # 叉包
+                "task_coordinates": {
+                    "pot_name": "N.20",
+                    "pot_x": 122.755687,
+                    "pot_y": 210.019149,
+                    "mark_pot_pose": -1.570920,
+                    "e_point_x": 0,
+                    "e_point_y": 0
+                },

+ 11 - 0
3rdparty/xapi/ros1/s3-作业-回e点

@@ -0,0 +1,11 @@
+                                'end_point_x': 122.8439634 - 50,  # e点需要往前开50
+                                'end_point_y': 198.54541533333332
+
+
+
+                "task_type": 105,  # 回e点
+                "task_coordinates": {
+                    "e_point_x": 122.8439634 - 50,
+                    "e_point_y": 198.54541533333332,
+                    "e_pose": 3.1415926,  # 固定值
+                },

+ 31 - 0
3rdparty/xapi/ros1/s4-行驶-e点-到-倒渣口2

@@ -0,0 +1,31 @@
+mqtt当前位置:'position_x': 109.30082456920857, 'position_y': 200.45750181311485
+
+
+                # 导航数据
+                "navigation": [
+                    {
+                        'type': 'forward',
+                        'nav_coordinates': [
+                            {
+                                'start_point_x': 109.30082456920857,  # mqtt中获取当前位置
+                                'start_point_y': 200.45750181311485,  # mqtt中获取当前位置
+                                'end_point_x': 18.618844,
+                                'end_point_y': 200.67567
+                            },
+                            {
+                                'start_point_x': 18.618844,
+                                'start_point_y': 200.67567,
+                                'center_point_x': 18.306224428571426,
+                                'center_point_y': 186.9142662857143,
+                                'end_point_x': 17.993604857142852,
+                                'end_point_y': 173.15286257142859
+                            },
+                            {
+                                'start_point_x': 17.993604857142852,
+                                'start_point_y': 173.15286257142859,
+                                'end_point_x': 47.9858831368238,
+                                'end_point_y': 172.47224256424514
+                            }
+                        ]
+                    }
+                ]

+ 18 - 0
3rdparty/xapi/ros1/s5-行驶-e点-到-倒渣口2

@@ -0,0 +1,18 @@
+mqtt当前位置:position_x': 50.189481964901745, 'position_y': 172.45461895435264
+
+
+                # 导航数据
+                "navigation": [
+
+                    {
+                        'type': 'backward',
+                        'nav_coordinates': [
+                            {
+                                'start_point_x': 49.46,  # 当前位置(铰链点)
+                                'start_point_y': 172.5,  # 当前位置(铰链点)
+                                'end_point_x': -2.487554142857145 - 0.8 - 0.5,  # 后车架
+                                'end_point_y': 173.02169257142856
+                            }
+                        ]
+                    }
+                ]

+ 43 - 0
3rdparty/xapi/ros1/s6-行驶-倒渣口2-到-n20的e点

@@ -0,0 +1,43 @@
+mqtt当前位置:position_x': 2.495681801706711, 'position_y': 173.16669248933502
+
+                                # e点位置
+                                'end_point_x': 122.8439634 - 50,  # e点需要往前开50
+                                'end_point_y': 198.54541533333332
+
+
+                # 导航数据
+                "navigation": [
+                    {
+                        'type': 'forward',
+                        'nav_coordinates': [
+                            {
+                                'start_point_x': 2.495,  # 当前位置
+                                'start_point_y': 173.1666142856,  # 当前位置
+                                'end_point_x': 182.6588877142857,
+                                'end_point_y': 169.8418064285714
+                            },
+                            {
+                                'start_point_x': 182.6588877142857,
+                                'start_point_y': 169.8418064285714,
+                                'center_point_x': 182.92185485714285,
+                                'center_point_y': 183.5769582142857,
+                                'end_point_x': 196.65700664285714,
+                                'end_point_y': 183.31399107142855
+                            },
+                            {
+                                'start_point_x': 196.65700664285714,
+                                'start_point_y': 183.31399107142855,
+                                'center_point_x': 182.92185485714285,
+                                'center_point_y': 183.5769582142857,
+                                'end_point_x': 183.184822,
+                                'end_point_y': 197.31211
+                            },
+                            {
+                                'start_point_x': 183.184822,
+                                'start_point_y': 197.31211,
+                                'end_point_x': 122.8439634 - 50,  # e点需要往前开50
+                                'end_point_y': 198.54541533333332
+                            }
+                        ]
+                    }
+                ]

+ 12 - 0
3rdparty/xapi/ros1/s7-作业-放包

@@ -0,0 +1,12 @@
+凯强 122.755687      210.019149     -1.570920
+
+
+                "task_type": 103,  # 放包
+                "task_coordinates": {
+                    "pot_name": "N.20",
+                    "pot_x": 122.755687,
+                    "pot_y": 210.019149,
+                    "mark_pot_pose": -1.570920,
+                    "e_point_x": 0,
+                    "e_point_y": 0
+                },

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 4 - 0
3rdparty/xapi/taiwuict/u1_for_aibox.py


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 4 - 0
3rdparty/xapi/taiwuict/u2_for_aibox.py


+ 833 - 0
3rdparty/xapi/taiwuict/u3_for_cscec.py

@@ -0,0 +1,833 @@
+# update: 2022-7-18
+"""
+中建八局一公司业务接口
+"""
+from urllib import parse
+import requests
+import time
+import traceback
+
+import sys
+import importlib
+
+sys.path.append('/home/server/projects/taiwuict/cscec-8bur-vms/supplement-python')
+# sys.path.append(r'D:\share\gitee\casper.supplement-python')  # for pc
+methods = importlib.import_module(f"libraries.base_original")
+
+
+class Api(object):
+
+    # def __init__(self, username='System001', password='Swdx@143',
+    def __init__(self, username='500A7062', password='198797#cjhxbin',
+                 project_id='d0f254cc-8196-499a-8f0d-52bf8ba91486',
+                 project_name='山东省公共卫生临床中心(济南市传染病医院二期)项目工程总承包(EPC)'):
+        """
+        第1现场:
+            济南市中心医院(东院区)项目工程总承包(EPC)
+            7eeb53a1-2bbd-4efa-8c8e-66936476226c
+
+        第2现场:
+            山东省大数据产业基地建设项目施工总承包
+            项目编号:500D10301958
+            ORGID:63729db2-ea9c-4096-b5d9-3ad0369903c8
+
+        第3现场:
+            山东省公共卫生临床中心(济南市传染病医院二期)项目工程总承包(EPC)
+            ORGID:d0f254cc-8196-499a-8f0d-52bf8ba91486
+
+        第4现场:
+            济南奥体东 13 及 17-2 号地块开发项目工程总 承包(EPC)
+            a8defc53-76b3-4f4e-931d-8dd35ba310c7
+
+        """
+
+        # --- define ---
+        self.service_url = 'http://app.cscec81.com/api'  # 正式环境
+        # self.service_url = f"http://pmdev.cscec81.com/api"  # 正式环境
+        # self.service_url = 'http://221.214.64.195:31229/api'  # 公网测试地址
+        # self.service_url = 'http://10.198.6.181:31229/api'  # vpn测试环境 https://221.214.64.195 gelubo01 Gelubo@2022
+
+        # --- release ---
+        self.project_id = project_id
+        self.project_name = project_name
+
+        # --- release ---
+        self.token = None
+        if username and password:
+            session_id, user_code, user_id = self.get_user_info(username, password)
+            self.token = f"Bearer {self.get_token(session_id, user_code, user_id)}"
+
+        # --- test ---
+        # self.token_test = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjAxMTgyODIsInVzZXJfbmFtZSI6Iui1tei_nuWxsTM0NTBhMjI0NmZiLTkwMWYtNGIwNi1hMjU2LTY4YTI4Y2VkZTU2Y2UwZDk5OTk3LTkzNzEtNGMwZi04NDNjLTUyNTMzYjE4ZTYxNSIsImp0aSI6Ijg3MzcxZTY0LTgwNzEtNGJlYy04YWMzLTUxMzViMTU5MzhjMyIsImNsaWVudF9pZCI6ImJyb3dzZXIiLCJzY29wZSI6WyJjbGllbnQiXX0.NF0IXabm_7RRqcdra6A7fsBvyBWa_62rkppQAFxalc4'
+
+    @staticmethod
+    def get_user_info(username, password):
+        """
+        获取用户信息
+        url: http://pmdev.cscec81.com/Login/Login.ashx?t=' + new Date().getTime() + '&method=CheckLogin
+        """
+        # url = f"{self.service_url}/Login/Login.ashx"
+        url = f"http://app.cscec81.com/Login/Login.ashx"  # 正式环境
+        # url = f"http://pm.cscec81.com/Login/Login.ashx"  # 测试环境
+        # url = f"http://221.214.64.195:31229/Login/Login.ashx"  # 测试环境
+        url += f"?t={int(time.time() * 1000)}&method=CheckLogin"
+        data = {
+            'Username': username,
+            'password': password,
+            'usertype': '0',
+            'sys': 'sitemanage',
+            'clientType': '5',
+        }
+        payload = parse.urlencode(data, encoding='utf-8')
+        headers = {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Cookie': 'ASP.NET_SessionId=luy3wg10b2kymc2nbiytn4xt'
+        }
+        print('Api.get_user_info.url:', url)
+        # print('Api.get_user_info.headers:', headers)
+        # print('Api.get_user_info.payload:', payload)
+        response = requests.request('POST', url, headers=headers, data=payload)
+
+        if response.status_code > 300:
+            print('Api.get_user_info.result:', response.status_code)
+            print('Api.get_user_info.detail:', response.text)
+            return '', '', ''
+        print('Api.get_user_info.result:', response.status_code)
+        json_data = response.json()
+        # print('Api.get_user_info.json_data:', json_data)
+        if not json_data or not json_data.get('data'):
+            return '', '', ''
+        session_id = json_data.get('data').get('ticketID')
+        user_code = json_data.get('data').get('userCode')
+        user_id = json_data.get('data').get('userID')
+        # print('Api.get_user_info.session_id:', session_id)
+        # print('Api.get_user_info.user_code:', user_code)
+        # print('Api.get_user_info.user_id:', user_id)
+        return session_id, user_code, user_id
+
+    @staticmethod
+    def get_token(session_id, user_code, user_id):
+        """
+        获取令牌
+        url: http://pmdev.cscec81.com/api/oauth/token?grant_type=session&sessionId="
+            + ticketID + "&userCode=" + userCode + "&userId=" + userID + "&client_type=web&system=1
+        """
+        # url = f"{self.service_url}/oauth/token"
+        url = f"http://app.cscec81.com/api/oauth/token"  # 正式环境
+        # url = f"http://pm.cscec81.com/api/oauth/token"  # 测试环境
+        url += f"?grant_type=session&sessionId={session_id}&userCode={user_code}&userId={user_id}"
+        url += f"&client_type=web&system=1"
+
+        headers = {
+            'Authorization': 'Basic YnJvd3Nlcjo='
+        }
+        print('Api.get_token.url:', url)
+        # print('Api.get_token.headers:', headers)
+        response = requests.post(url=url, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.get_token.result:', response.status_code)
+            print('Api.get_token.detail:', response.text)
+            return ''
+        else:
+            print('Api.get_token.result:', response.status_code)
+            # return response.headers.get('authorization')
+            return response.json().get('access_token')
+
+    def get_cscec8_user_list(self):
+        """
+        获取八局管理人员
+        /api/economic/indoor/getProjectUserList?projectId=05037f9b-5cb6-4a43-9410-c0e6a83f280e
+        return [
+            {
+                cardId: 身份证号
+                imgUrl: 人脸图片
+                dutyId: 职务id
+                dutyName: 职务名称
+            }
+        ]
+        """
+        url = f"http://app.cscec81.com/api/economic/indoor/getProjectUserFaceList?projectId={self.project_id}"
+
+        print('Api.get_cscec8_user_list.url:', url)
+        response = requests.get(url, headers={'content-type': 'application/json'})
+
+        if response.status_code > 300:
+            print('Api.get_cscec8_user_list.result:', response.status_code)
+            print('Api.get_cscec8_user_list.detail:', response.text)
+            return []
+
+        json_data = response.json()
+        # print(json_data)
+        return json_data
+
+    def get_worker_list(self):
+        """
+        获取工人列表(云筑网工人信息)
+        url: http://app.cscec81.com/api/safeeducation/workerInfo/workers?projectName=济南市中心医院(东院区)项目工程总承包(EPC)
+        url: http://221.214.64.195:31229/api/safeeducation/workerInfo/workers?projectName=丽泽SOHO项目
+        return [
+            {
+                headImagePath: 人脸图像地址
+                subContractorName: 劳务公司
+                workTypeName: 工种
+
+            }
+        ]
+        """
+        url = f"http://app.cscec81.com/api/safeeducation/workerInfo/workers?projectName={self.project_name}"
+        url += f"&page=0&size=5000"
+
+        print('Api.get_worker_list.url:', url)
+        response = requests.get(url, headers={'content-type': 'application/json'})
+        print('Api.get_worker_list.result:', response.status_code)
+
+        if response.status_code > 300:
+            print('Api.get_worker_list.result:', response.status_code)
+            print('Api.get_worker_list.detail:', response.text)
+            return []
+
+        json_data = response.json()
+        # {'projectName': '济南市中心医院(东院区)项目工程总承包(EPC)', 'workerName': '高玉锋',
+        # 'idCardNumber': '372527198311022813', 'workTypeName': '电气设备安装工', 'headImagePath': None}
+        if not json_data or not json_data.get('success') or not json_data.get('data'):
+            return []
+        if not json_data.get('data').get('content'):
+            return []
+        # print(json_data.get('data').keys())
+        # print(json_data.get('data').get('totalElements'))
+        # print(len(json_data.get('data').get('content')))
+        # print(json_data.get('data').get('content')[0])
+        return json_data.get('data').get('content')
+
+    @staticmethod
+    def get_face_image_by_path(image_path):
+        """
+        获取工人照片
+        url: https://lwres.yzw.cn/worker-avatar/Original/2018/0612/f3230a72-57ab-4aaf-a741-61d8c078fe89.jpg
+        """
+        while True:
+            try:
+
+                url = f"https://lwres.yzw.cn/{image_path}"
+                # print('Api.get_face_image_by_path.url:', url)
+                response = requests.get(url, headers={'content-type': 'application/json'})
+                # print('Api.get_face_image_by_path.result:', response.status_code)
+
+                if response.status_code > 300:
+                    print('Api.get_face_image_by_path.result:', response.status_code)
+                    print('Api.get_face_image_by_path.detail:', response.text)
+                    return b''
+                else:
+                    return response.content
+
+            except Exception as exception:
+
+                if exception.__class__.__name__ == 'ConnectionError':
+                    print(f"Api.get_face_image_by_path: sleep 60s")
+                    time.sleep(60)
+                else:
+                    print(f"Api.get_face_image_by_path.exception:{exception.__class__.__name__}")
+                    print(f"Api.get_face_image_by_path.traceback:{traceback.format_exc()}")
+                    return b''
+
+    @staticmethod
+    def get_face_image_by_path_v2(image_path):
+        """
+        获取工人高清头像
+        url: http://app.cscec81.com/media/202110/25/20211025-161726-381-4862.jpg
+        """
+        while True:
+            try:
+
+                url = f"http://app.cscec81.com/media/{image_path}"
+                # print('Api.get_face_image_by_path_v2.url:', url)
+                response = requests.get(url, headers={'content-type': 'application/json'})
+                # print('Api.get_face_image_by_path_v2.result:', response.status_code)
+
+                if response.status_code > 300:
+                    print('Api.get_face_image_by_path_v2.result:', response.status_code)
+                    print('Api.get_face_image_by_path_v2.detail:', response.text)
+                    return b''
+                else:
+                    return response.content
+
+            except Exception as exception:
+
+                if exception.__class__.__name__ == 'ConnectionError':
+                    print(f"Api.get_face_image_by_path_v2: sleep 60s")
+                    time.sleep(60)
+                else:
+                    print(f"Api.get_face_image_by_path_v2.exception:{exception.__class__.__name__}")
+                    print(f"Api.get_face_image_by_path_v2.traceback:{traceback.format_exc()}")
+                    return b''
+
+    @staticmethod
+    def get_face_image_by_url(image_url):
+        """
+        获取八局管理人员人脸图像
+        url: https://hcm.cscec81.com/img?type=photo&size=120&index=ae2f8424-c63f-11ea-b0a9-ea6f5cf6388d&rnd=1653618903725
+        """
+        while True:
+            try:
+
+                # print('Api.get_face_image_by_url.url:', url)
+                response = requests.get(image_url, headers={'content-type': 'application/json'})
+                # print('Api.get_face_image_by_url.result:', response.status_code)
+
+                if response.status_code > 300:
+                    print('Api.get_face_image_by_url.result:', response.status_code)
+                    print('Api.get_face_image_by_url.detail:', response.text)
+                    return b''
+                else:
+                    return response.content
+
+            except Exception as exception:
+
+                if exception.__class__.__name__ == 'ConnectionError':
+                    print(f"Api.get_face_image_by_url: sleep 60s")
+                    time.sleep(60)
+                else:
+                    print(f"Api.get_face_image_by_url.exception:{exception.__class__.__name__}")
+                    print(f"Api.get_face_image_by_url.traceback:{traceback.format_exc()}")
+                    return b''
+
+    def upload_file(self, face_uuid, file_path):
+        """
+        上传文件图片
+        """
+        # service_url = 'http://221.214.64.195:31229/api'
+        # url = f"{service_url}/storages/files/anonymousUpload"
+        url = f"{self.service_url}/storages/files/anonymousUpload"
+        files = {
+            'id': (None, face_uuid),
+            'file': ('1.jpg', open(file_path, 'rb'), 'image/jpg'),
+        }
+        headers = {
+            'Authorization': self.token,
+        }
+        print('Api.upload_file.url:', url)
+        response = requests.post(url=url, files=files, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.upload_file.result:', response.status_code)
+            # print('Api.upload_file.detail:', response.text)
+            return ''
+        else:
+            print('Api.upload_file.result:', response.status_code)
+            """
+            原地址
+            http://10.198.6.181:31900/cscec81-cloud/202110/12/20211012-161022-713-7035.jpg
+            http://10.198.6.173:30900/cscec81-cloud/202206/13/20220613-022118-96-3795.jpg
+            替换后地址
+            http://221.214.64.195:31229/media/202110/12/20211012-161022-713-7035.jpg
+            http://app.cscec81.com/media/202110/12/20211012-161022-713-7035.jpg
+            """
+            link = response.json().get('link')
+            # link = link.replace('http://10.198.6.181:31900/cscec81-cloud', 'http://221.214.64.195:31229/media')  # 测试
+            # link = link.replace('http://10.198.6.173:30900/cscec81-cloud', 'http://app.cscec81.com/media')  # 正式
+            # link = f"http://221.214.64.195:31229/media{link.split('cscec81-cloud')[1]}"  # 测试环境
+            link = f"http://app.cscec81.com/media{link.split('cscec81-cloud')[1]}"  # 正式环境
+            return link
+
+    def get_user_list(self):
+        """
+        获取在职人员列表
+        """
+        url = f"{self.service_url}/system/users/getUserList"
+        data = {
+            'companyId': self.project_id,  # 单位id
+            'userName': '',  # 人员名称
+            'isMainPosition': '',  # 1 主职 0 主职+兼职
+            'postId': '',  # 职务
+            'postName': '',
+        }
+        headers = {
+            'Authorization': self.token
+        }
+        # print('Api.get_user_list.url:', url)
+        response = requests.get(url, params=data, headers=headers)
+        # print('Api.get_user_list.result:', response.status_code)
+
+        if response.status_code > 300:
+            print('Api.get_user_list.result:', response.status_code)
+            print('Api.get_user_list.detail:', response.text)
+            return []
+        json_data = response.json()
+        if not json_data.get('content'):
+            return []
+        print(json_data.get('content')[0])
+        return json_data.get('content')
+
+    def push_face(self, send_at, face_file_link, face_name, cscec8_duty_name, cscec8_id, age, group, source):
+        """
+        上传新增人脸(上传八局管理人员接口)
+        """
+        # test_service_url = 'http://10.198.6.181:31229/api'
+        # url = f"{test_service_url}/worker/userInfo"
+        url = f"{self.service_url}/worker/userInfo"
+        data = [{
+            'groupId': group,  # 分组ID 001 陌生人 002 劳务工人 003 临时用工 004 项目管理人员 005 VIP 006 黑名单 007 其他
+            'equipmentId': self.project_id,  # 设备id
+            'userId': cscec8_id,  # 用户唯一标识 人脸id
+            'userName': face_name,  # 用户名 人脸名称
+            'age': age,  # 年龄
+            'postInfoName': cscec8_duty_name,  # 职务
+            'lastEnterTime': send_at,  # 最后进入时间 '2021-08-10 08:00:00'
+            'faceInfo': face_file_link,  # 文件链接地址
+            'sourceType': source,  # 是否是八局人脸库信息 0 不是 1 是
+        }]
+        # print('Api.push_face:data:', data)
+        # methods.debug_log('Api.push_face', f"m-418 | data: {data}")
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        # print('Api.push_face.url:', url)
+        response = requests.post(url, json=data, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.push_face.result:', response.status_code)
+            print('Api.push_face.detail:', response.text)
+            methods.debug_log('apis.u3_for_cscec', f"m-395: url -> {url}")
+            methods.debug_log('apis.u3_for_cscec', f"m-395: data -> {data}")
+            methods.debug_log('apis.u3_for_cscec', f"m-395: result -> {response.status_code}")
+            methods.debug_log('apis.u3_for_cscec', f"m-395: detail -> {response.text}")
+            return False
+        else:
+            # print('Api.push_face.result:', response.status_code)
+            # print('Api.push_face:json:', response.json())
+            return True
+
+    def push_work_info(self, send_at, face_file_link, face_name, prc_id, worker_contractor, worker_type_name, age,
+                       find_at):
+        """
+        添加劳务人员接口(上传农民工接口)
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/userInfo/workerInfo"
+        url = f"{self.service_url}/worker/userInfo/workerInfo"
+        data = [{
+            'equipmentId': self.project_id,  # 设备id
+            'userId': prc_id,  # 用户唯一标识 农民工身份证id
+            'userName': face_name,  # 用户名 工人名称
+            'userCerd': prc_id,  # 用户身份证号
+            'age': age,  # 年龄
+            'postInfoName': '',  # 职务
+            'workType': worker_type_name,  # 工种(农民工)
+            'labourCompany': worker_contractor,  # 劳务公司(农民工)
+            'enterState': '',  # 进场状态
+            'enterTime': '',  # 进场时间
+            'synTime': find_at,  # 同步时间(同步云筑网时间)
+            'lastEnterTime': send_at,  # 最后进入时间 '2021-08-10 08:00:00'
+            'faceInfo': face_file_link,  # 文件链接地址 人脸信息(图片传八局服务器系统,入参只传服务器文件地址)
+            'labourSource': str(0),  # 是否八局底库人员 0 不是 1 是
+        }]
+        # data = [
+        #     {'equipmentId': '63729db2-ea9c-4096-b5d9-3ad0369903c8', 'userId': '511025199009011013', 'userName': '赵润',
+        #      'userCerd': '511025199009011013', 'age': '', 'postInfoName': '', 'workType': None, 'labourCompany': None,
+        #      'enterState': '', 'enterTime': '', 'synTime': '', 'lastEnterTime': '2022-05-27 10:10:56',
+        #      'faceInfo': 'http://221.214.64.195:31229/media/202205/27/20220527-150651-60-5508.jpg',
+        #      'labourSource': '0'}]
+        # print('Api.push_work_info:data:', data)
+        # methods.debug_log('Api.push_work_info', f"m-464 | data: {data}")
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        # print('Api.push_work_info.url:', url)
+        response = requests.post(url, json=data, headers=headers)
+
+        if response.status_code > 300:
+            # print('Api.push_work_info.result:', response.status_code)
+            # print('Api.push_work_info.detail:', response.text)
+            methods.debug_log('apis.u3_for_cscec', f"m-457: url -> {url}")
+            methods.debug_log('apis.u3_for_cscec', f"m-457: data -> {data}")
+            methods.debug_log('apis.u3_for_cscec', f"m-457: result -> {response.status_code}")
+            methods.debug_log('apis.u3_for_cscec', f"m-457: detail -> {response.text}")
+            return False
+        else:
+            # print('Api.push_work_info:json:', response.json())
+            return True
+
+    def pull_update_info(self, start_at):
+        """
+        获取线上更新数据 todo 定时请求更新本地数据相关的数据
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/userInfo/findUpdateUserByTime"
+        url = f"{self.service_url}/worker/userInfo/findUpdateUserByTime"
+        # url = f"http://425ax87563.zicp.vip/worker/userInfo/findUpdateUserByTime?queryStartTime=2022-05-27 11:09:00"
+        # url += '?queryStartTime=2022-05-27 11:09:00'
+        url += f"?queryStartTime={start_at}"
+        data = {
+            # 'companyId': self.project_id,  # 单位id
+            # 'queryStartTime': '2021-10-21 11:09:00',
+            # 'queryStartTime': '2022-05-27 11:09:00',
+        }
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        print('Api.pull_update_info.url:', url)
+        response = requests.get(url, params=data, headers=headers)
+        print('Api.pull_update_info.result:', response.status_code)
+        print('Api.pull_update_info:text:', response.json())
+        return []
+        # if response.status_code > 300:
+        #     print('Api.pull_update_info:text:', response.text)
+        #     return []
+        # json_data = response.json()
+        # if not json_data.get('content'):
+        #     return []
+        # print(json_data.get('content')[0])
+        # return json_data.get('content')
+
+    def pull_alarm_list(self, create_at):
+        """
+        获取告警列表
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/alarm/alarmPageList"
+        # url += f"?createTime={create_at}"
+        url = f"{self.service_url}/worker/alarm/alarmPageList"
+        url += f"?createTime={create_at}"
+        params = {
+            # 'createTime': '2021-08-10 08:00:00'
+        }
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        print('Api.pull_alarm_list.url:', url)
+        response = requests.get(url, params=params, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.pull_alarm_list.result:', response.status_code)
+            print('Api.pull_alarm_list.detail:', response.text)
+            return {}
+        else:
+            print('Api.pull_alarm_list.result:', response.status_code)
+            return response.json()
+
+    def pull_alarm_group_list(self, create_at):
+        """
+        获取报警分组列表
+        return [
+            {
+                ALARM_GROUP_STRATEGY_IDS: 1 现场屏显报警 2 现场语音报警 3 微信报警
+            }
+        ]
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/alarm/alarmGroupList"
+        url = f"{self.service_url}/worker/alarm/alarmGroupList"
+        params = {
+            'projectId': self.project_id,
+            'createTime': create_at,
+        }
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        print('Api.pull_alarm_group_list.url:', url)
+        response = requests.get(url, params=params, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.pull_alarm_group_list.result:', response.status_code)
+            print('Api.pull_alarm_group_list.detail:', response.text)
+            return []
+        else:
+            print('Api.pull_alarm_group_list.result:', response.status_code)
+            return response.json()
+
+    def push_message(self):
+        """
+        推送微信消息
+        {'ALARM_TYPE': 1, 'ALARM_USER_ID': 'f6bae517-0a28-429c-b65b-b16548ab2ca4', 'ALARM_GROUP_NAME': '屏显报警',
+        'id': '17ee224382634361bc5b9ef41ce09eb1', 'ALARM_STRATEGY_GROUP_ID': '5e58ed8828c84c779b3cddb0257d6f5f',
+        'ALARM_PROJECT_ID': '63729db2-ea9c-4096-b5d9-3ad0369903c8', 'ALARM_NAME': '刘美琪'}
+        {'ALARM_TYPE': 1, 'ALARM_USER_ID': '7a574580-6af1-40af-a3c8-1c0d5aee567b', 'ALARM_GROUP_NAME': '微信报警',
+        'id': '04bb6b4628954b2282b15a3cb2e74ab8', 'ALARM_STRATEGY_GROUP_ID': '0f695744b43447b2b4cbad8dc78389f4',
+        'ALARM_PROJECT_ID': '63729db2-ea9c-4096-b5d9-3ad0369903c8', 'ALARM_NAME': '吴非'}
+
+        微信推送结果查询接口 worker/alarm/getWxPushState
+        状态 00 未发送 01 发送成功 02 发送失败 03 发送异常
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/alarm/getWxAlarmUser"
+        url = f"{self.service_url}/worker/alarm/getWxAlarmUser"
+        data = {
+            'projectId': self.project_id,  # 项目id
+            # 'groupId': '5e58ed8828c84c779b3cddb0257d6f5f',  # 告警组id ALARM_STRATEGY_GROUP_ID
+            'groupId': '0f695744b43447b2b4cbad8dc78389f4',  # 告警组id ALARM_STRATEGY_GROUP_ID
+            # 'userId': 'f6bae517-0a28-429c-b65b-b16548ab2ca4',  # 告警用户id ALARM_USER_ID
+            'userId': '7a574580-6af1-40af-a3c8-1c0d5aee567b',  # 告警用户id ALARM_USER_ID
+        }
+        print(f"Api.push_message:data: {data}")
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        print('Api.push_message.url:', url)
+        response = requests.post(url, json=data, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.push_message.result:', response.status_code)
+            print('Api.push_message.detail:', response.text)
+            return {}
+        else:
+            print('Api.push_message.result:', response.status_code)
+            return response.json()
+
+    def check_message_state(self):
+        """
+        微信推送结果查询接口 worker/alarm/getWxPushState
+        状态 00 未发送 01 发送成功 02 发送失败 03 发送异常
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/alarm/getWxPushState"
+        url = f"{self.service_url}/worker/alarm/getWxPushState"
+        # url += f"?wxPushId=24290bde-09d7-444b-b2ea-039213f8b628"
+        url += f"?wxPushId=77c36ad2-ad35-464b-bf52-6d16fd9c74e0"
+        data = {
+            # 'wxPushId': mid,
+            # 'wxPushId': '24290bde-09d7-444b-b2ea-039213f8b628',
+        }
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        print('Api.check_message_state.url:', url)
+        # response = requests.get(url, json=data, headers=headers)
+        response = requests.get(url, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.check_message_state.result:', response.status_code)
+            print('Api.check_message_state.detail:', response.text)
+            return {}
+        else:
+            print('Api.check_message_state.result:', response.status_code)
+            # return response.json()
+            return response.text
+
+    def push_state(self):
+        """
+        每分钟发送心跳请求接口
+        """
+        # test_service_url = 'http://425ax87563.zicp.vip'
+        # url = f"{test_service_url}/worker/equipmentMaintain/updateEquStateTime"
+        url = f"{self.service_url}/worker/equipmentMaintain/updateEquStateTime"
+        data = {
+            'projectId': self.project_id,
+        }
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        # print('Api.push_state.url:', url)
+        response = requests.post(url, json=data, headers=headers)
+
+        if response.status_code > 300:
+            print('Api.push_state.result:', response.status_code)
+            print('Api.push_state.detail:', response.text)
+            return {}
+        else:
+            # print('Api.push_state.result:', response.status_code)
+            return response.json()
+
+    def push_face_log(self, user_id, find_at, face_link=''):
+        """
+        上传识别记录
+        methods.ts_to_string(item.get('create_at'), '%Y-%m-%d %H:%M:%S')
+        """
+        # test_service_url = 'http://10.198.6.181:31229/api'
+        # url = f"{test_service_url}/worker/ledger/addLedger"
+        url = f"{self.service_url}/worker/ledger/addLedger"
+        data = [{
+            'projectId': self.project_id,  # 项目id
+            'equipmentInfo': self.project_id,  # 设备id
+            'equipmentRecognitionTime': find_at,  # 识别时间 '2021-10-21 11:09:00'
+            'userId': user_id,  # 人脸标识 八局人员使用uuid 劳务人员使用身份证号
+            'faceInfo': face_link,  # 文件链接地址 人脸信息(图片传八局服务器系统,入参只传服务器文件地址)
+        }]
+        headers = {
+            'Authorization': self.token,
+            # 'Authorization': self.token_test,
+        }
+        # methods.debug_log('apis.u3_for_cscec', f"m-651: project_id -> {self.project_id}")
+        print('Api.push_face_log.url:', url)
+        # print('Api.push_face_log:data:', data)
+        response = requests.post(url, json=data, headers=headers)
+
+        if response.status_code > 300:
+            # print('Api.push_face_log.result:', response.status_code)
+            # print('Api.push_face_log.detail:', response.text)
+            methods.debug_log('apis.u3_for_cscec', f"m-678: url -> {url}")
+            methods.debug_log('apis.u3_for_cscec', f"m-678: data -> {data}")
+            methods.debug_log('apis.u3_for_cscec', f"m-678: result -> {response.status_code}")
+            methods.debug_log('apis.u3_for_cscec', f"m-678: detail -> {response.text}")
+            return False
+        else:
+            print('Api.push_face_log.result:', response.status_code)
+            print('Api.push_face_log.detail:', response.text)
+            # methods.debug_log('apis.u3_for_cscec', f"m-668: result -> {response.status_code}")
+            # methods.debug_log('apis.u3_for_cscec', f"m-668: detail -> {response.text}")
+            # methods.debug_log('apis.u3_for_cscec', f"m-668: detail -> {response.json()}")
+            return True
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # api = Api(username='500A4475', password='cscec81#')
+    # api = Api(username='500A7062', password='198797#cjhxbin')  # 侯经理 正式环境
+    api = Api(project_id='63729db2-ea9c-4096-b5d9-3ad0369903c8',
+              project_name='山东省大数据产业基地建设项目施工总承包')  # 正式环境账号
+    # api = Api(project_id='a8defc53-76b3-4f4e-931d-8dd35ba310c7',
+    #           project_name='济南奥体东 13 及 17-2 号地块开发项目工程总 承包(EPC)')  # 正式环境账号
+    # api = Api()  # 正式环境账号
+
+    # --- test ---
+    # out = api.upload_file('aabbccdd-aabbccss-ssggaaff-sseeqqhh', r"D:\3.jpg")
+    # print(out)
+
+    # --- test ---
+    # api.push_state()
+    # api.push_face(
+    #     send_at='2022-07-18 08:09:00',
+    #     face_file_link='http://app.cscec81.com/media/202207/18/20220718-105339-394-2140.jpg',
+    #     face_name='张三001',
+    #     cscec8_duty_name='司机',
+    #     cscec8_id='aabbccdd-aabbccss-ssggaaff-sseeqqhh',
+    #     age=60,
+    #     group='004',
+    #     source=1)
+    # api.push_face_log('aabbccdd-aabbccss-ssggaaff-sseeqqhh', '2022-06-02 08:09:01',
+    #                   'http://app.cscec81.com/media/202207/18/20220718-111202-730-7523.jpg')
+
+    # --- test ---
+    # _dict = {}
+    # items = api.get_worker_list()
+    # print(items[0])
+    # print(len(items))
+    # for item in items:
+    #     _dict[item.get('idCardNumber')] = 1
+    # print(len(_dict.keys()))
+
+    # --- check 八局人员 ---
+    # items = api.get_cscec8_user_list()
+    # for item in items:
+    #     if item.get('userName') in ['孙会沅']:
+    #         print(item)
+
+    # --- check avatarAddress ---
+    # out = api.get_worker_list()
+    # for one in out:
+    #     if one.get('workerName') in ['鲁成', '韩继涛', '丁保树', '金义莲']:
+    #         print(one)
+    #     # if one.get('subContractorName'):
+    #     #     print(one.get('avatarAddress'))
+    #     #     print(one.get('flag'), type(one.get('flag')))
+    #     #     image = api.get_face_image_by_path_v2(one.get('avatarAddress'))
+    #     #     print(type(image), len(image))
+
+    # --- test ---
+    # uuid = '62a166f324bfcf82f7066b65'
+    # face_image_path = '/home/server/resources/vms-files/2022-0609-112017-618668-raw.jpg'
+    # face_image_path = '/home/server/resources/vms-files/2022-0601-172536-193936.jpg'
+    # out = api.upload_file(uuid, face_image_path)
+    # print(out)
+
+    # --- test ---
+    # o = api.pull_alarm_list('2021-06-01 08:00:00')
+    # print(o)
+    # o = api.pull_alarm_group_list('2021-06-01 08:00:00')
+    # print(o)
+    # o = api.push_message()
+    # print(o)
+    # o = api.check_message_state()
+    # print(o)
+
+    # --- test ---
+    # o = api.pull_update_info('2022-05-27 11:09:00')
+    # o = api.pull_alarm_list('2022-05-27 11:09:00')
+    # o = api.pull_alarm_group_list()
+    # print(o)
+
+    # --- test ---
+    # o = api.push_work_info(1, 2, 3, 4, 1, 1)
+    # print(o)
+
+    # --- test ---
+    # u = 'https://hcm.cscec81.com/img?type=photo&size=120&index=ae2f8424-c63f-11ea-b0a9-ea6f5cf6388d&rnd=1653618903725'
+    # u = 'https://hcm.cscec81.com/img?type=photo&size=120&index=6605f16a-095a-11ec-99ad-da7e2f6980d1&rnd=1653620550071'
+    # o = api.get_face_image_by_url(u)
+    # print(o)
+
+    # --- test ---
+    # out = api.get_cscec8_user_list()
+    # print(len(out))
+    # for i in out:
+    #     if i.get('userName') == '杜兴昌':
+    #         print(i)
+
+    # --- test ---
+    # out = api.push_face('6166f0ab95a68293064d97c4', '2021-04-14 14:43:55',
+    #                     'http://221.214.64.195:31229/media/202110/21/20211021-120733-570-3862.jpg', 1, '张001')
+    # out = api.push_work_info('6166f0ab95a68293064d97c4', '2021-10-13 14:43:55',
+    #                          'http://221.214.64.195:31229/media/202110/21/20211021-120733-570-3862.jpg')
+    # print(out)
+
+    # --- test ---
+    # out = api.pull_alarm_group_list()
+    # print(out)
+
+    # --- test ---
+    # alarm_group_id = '1dfcd494affd4ee9aeb533c2d12558e7'
+    # out = api.push_message(alarm_group_id)
+
+    # --- test ---
+    # out = api.pull_alarm_group_list()
+    # out = api.get_user_list()
+    # print(out)
+
+    # --- test ---
+    # out = api.get_user_list()
+    # print(out)
+
+    # --- test ---
+    # def write_bytes(path, data=b''):
+    #     with open(path, 'wb') as f:
+    #         f.write(data)
+    # outputs = api.get_worker_list_by_project_name('济南市中心医院(东院区)项目工程总承包(EPC)')
+    # count = 0
+    # for i in outputs:
+    #     if not i.get('headImagePath'):
+    #         continue
+    #     url = f"https://lwres.yzw.cn/{i.get('headImagePath')}"
+    #     r = requests.get(url, headers={'content-type': 'application/json'})
+    #     count += 1
+    #     name = str(count).zfill(6)
+    #     write_bytes(f"D:\\test\\111\\{name}.jpg", r.content)
+
+    # --- test ---
+    # def write_bytes(path, data=b''):
+    #     with open(path, 'wb') as f:
+    #         f.write(data)
+    # _url = f"https://lwres.yzw.cn/worker-avatar/Original/2021/0303/0f735c37-118f-4cd8-a0a5-753e0e2fc7d7.jpg"
+    # r = requests.get(_url, headers={'content-type': 'application/json'})
+    # write_bytes(f"D:\\test\\111\\{1}.jpg", r.content)
+
+    # --- test ---
+    # d1 = {}
+    # outputs = api.get_worker_list_by_project_name('济南市中心医院(东院区)项目工程总承包(EPC)')
+    # for i in outputs:
+    #     if i.get('workTypeName') not in d1:
+    #         d1[i.get('workTypeName')] = 0
+    #     d1[i.get('workTypeName')] += 1
+    # print(d1)
+
+    # --- test ---
+    # api.get_image('worker-avatar/Original/2018/0612/f3230a72-57ab-4aaf-a741-61d8c078fe89.jpg')
+    # out = api.get_worker_list_by_project_name('济南市中心医院(东院区)项目工程总承包(EPC)')
+    # print(len(out))

+ 222 - 0
3rdparty/xclient/xinflux.py

@@ -0,0 +1,222 @@
+# update: 2021-8-23-14
+# see: https://github.com/influxdata/influxdb-python - pip install influxdb
+# see: https://pypi.org/project/influxdb/#history
+# see: https://registry.hub.docker.com/_/influxdb
+# see: https://influxdb-python.readthedocs.io/en/latest/include-readme.html
+# see: https://github.com/unaizalakain/qinfluxdb - influxdb orm
+# see: https://blog.csdn.net/liuming690452074/article/details/115719884 - delete
+# see: https://blog.csdn.net/xiligey1/article/details/104530893/
+# see: https://blog.csdn.net/yue530tomtom/article/details/82688453 - func
+"""
+"""
+from influxdb import InfluxDBClient
+import datetime
+import time
+
+
+class Client(InfluxDBClient):
+
+    def __init__(self, host='fra-middleware-influx', port=8086, database='vms'):
+        """
+        内部访问:
+            host: fra-middleware-influx
+            port: 8086
+        远程访问:
+            host: 192.168.30.59
+            port: 7080
+        """
+        """
+        def filter
+        def remove
+        def append
+        def add
+
+        def write
+        def read
+        def save
+        def add
+        def set
+        def get
+        def put
+
+        def craete
+        def delete
+        def update
+        def select
+        """
+        self.database = database
+        super().__init__(host, port, 'admin', 'admin', database)
+        self.create_database(database)
+
+    def add_item(self, table, key_dict=dict(), value_dict=dict()):
+        """保存单条"""
+        _points = [
+            {
+                'measurement': table,  # 表名
+                'time': datetime.datetime.now().isoformat('T'),  # iso标准格式
+                'tags': key_dict,
+                'fields': value_dict
+            }
+        ]
+        return self.write_points(_points, database=self.database)
+
+    def add_items(self, table, key_list=list(), value_list=list()):
+        """保存多条"""
+        _points = list()
+        for key, value in zip(key_list, value_list):
+            _point = {
+                'measurement': table,  # 表名
+                'time': datetime.datetime.now().isoformat('T'),  # iso标准格式
+                'tags': key,
+                'fields': value
+            }
+            _points.append(_point)
+        return self.write_points(_points, database=self.database)
+
+    def filter_by_time_range(self, table, start_ts, end_ts):
+        """按时时间筛选"""
+        start_at = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime(start_ts))
+        end_at = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.localtime(end_ts))
+        sql = f"select * from {table} "
+        sql += f"where time >= '{start_at}' and time <= '{end_at}'"
+        sql += f'order by time desc;'
+        result = self.query(sql, database=self.database)
+        return result.get_points()
+
+    def remove_one(self, table, k, v):  # todo 不可用
+        """按时时间筛选"""
+        sql = f"delete from {table} where \"{k}\"=\"{v}\";"
+        return self.query(sql, database=self.database)
+
+    def delete_table(self, table):
+        return self.drop_measurement(table)
+
+
+if __name__ == '__main__':
+    """
+    sudo python3 -m pip install influxdb
+    sudo python3 /home/server/projects/taiwuict/cscec-8bur-vms/supplement-python/clients/db_influx.py
+    """
+    import time
+
+    # --- init ---
+    # xdb = Client(host='192.168.30.59', port=7080, database='vms')
+    # xdb = Client(host='192.168.30.169', port=7080, database='vms')
+    # xdb = Client(host='192.168.30.13', port=7080, database='vms')
+    # xdb = Client(host='192.168.20.231', port=7080, database='vms')
+    # xdb = Client(host='192.168.0.15', port=7080, database='vms')
+    xdb = Client(host='192.168.1.242', port=7080, database='vms')  # 第4现场
+
+    # --- test ---
+    s = time.time() - (86400 * 100)
+    e = time.time()
+    out = xdb.filter_by_time_range('FaceLog', s, e)
+    for i in out:
+        print(i)
+        break
+
+    # --- test ---
+    # s = 1649606400
+    # e = 1650297600
+    # out = xdb.filter_by_time_range('FaceLog', s, e)
+    # for i in out:
+    #     print(i)
+    #     break
+
+    # --- test filter_by_time_range ---
+    # s = time.mktime(time.strptime('2020-01-09 13:45:00', '%Y-%m-%d %H:%M:%S'))
+    # e = time.mktime(time.strptime('2022-01-01 17:00:00', '%Y-%m-%d %H:%M:%S'))
+    # out = xdb.filter_by_time_range('FaceLog', s, e)
+    # for i in out:
+    #     print(i)
+    #     break
+
+    # --- test 添加数据 ---
+    # out = xdb.add_item('FaceLog', {}, {'aaa': 111, 'bbb': 222})
+    # print(out)
+
+    # --- test delete_table ---
+    # xdb.delete_table('FaceLog')
+
+    # --- test remove_one ---
+    # xdb.remove_one('FaceLog', 'face_uuid', 'aaabbbccc')
+
+    # --- test 获取数据库列表 ---
+    # out = xdb.get_list_database()
+    # print(out)
+
+    # --- test 创建数据库 ---
+    # xdb.create_database('vms')
+    # out = xdb.get_list_database()
+    # print(out)
+
+    # --- test 删除数据库 ---
+    # out1 = xdb.drop_database('vms1')
+    # out = xdb.get_list_database()
+    # print(out1, out)
+
+    # --- test 显示数据库中的表 ---
+    # out = xdb.query('show measurements;', database=xdb.database)
+    # print(out)
+
+    # --- test 添加数据 ---
+    # points = [
+    #     {
+    #         # "time": datetime.datetime.utcnow().isoformat('T'),  # iso标准时间
+    #         "time": datetime.datetime.now().isoformat('T'),  # iso标准时间
+    #         "measurement": "table23",  # 表名
+    #         "tags": {
+    #             "key1": "server01",
+    #             "key2": "us-west"
+    #         },
+    #         "fields": {
+    #             "field1": 0.64,
+    #             "field2": 0.42,
+    #         }
+    #     }
+    # ]  # 待写入数据库的点组成的列表
+    # out = xdb.write_points(points, database=xdb.database)  # 将这些点写入指定database
+    # print(out)
+
+    # --- test 删除表数据 ---
+    # table_name = 'students'
+    # xdb.query(f"drop measurement {table_name}")  # 删除表
+
+    # --- test query ---
+    # sql = f"select <字段名> from <数据库.表名> "
+    # sql += f"where time >= '<开始时间>' and time <= '<结束时间>' "
+    # sql += f"TZ('Asia/Shanghai')"
+    # out = xdb.query(sql, database='vms')
+    # print(out)
+
+    # --- test ---
+    # now = datetime.datetime.utcnow().isoformat('T')
+    # print(now, type(now))
+
+    # --- test ---
+    # _sql = f"select field1, field2 from table23 "
+    # _sql += f"where time >= '{start_at}' and time <= '{end_at}' "
+    # _sql += f"TZ('Asia/Shanghai');"
+    # out = xdb.query(_sql, database=xdb.database)
+    # print(out)
+
+    # --- test add_item ---
+    # _key_dict = {
+    #     "key1": "server01",
+    #     "key2": "server02",
+    # }
+    # _value_dict = {
+    #     "field1": 0.64,
+    #     "field2": 0.55,
+    # }
+    # out = xdb.add_item('table123', _key_dict, _value_dict)
+    # print(out)
+
+    # --- test add_item ---
+    # _key_dict = {
+    #     'face_uuid': 'aaabbbccc',
+    # }
+    # _value_dict = {
+    #     'count': 1,
+    # }
+    # xdb.add_item('FaceLog', _key_dict, _value_dict)

+ 93 - 0
3rdparty/xclient/xmaria.py

@@ -0,0 +1,93 @@
+# update: 2021-7-2-9
+"""
+pymysql==0.9.3
+peewee==3.13.1  # db orm
+"""
+from peewee import MySQLDatabase as PeeweeEngine  # todo UserWarning: Unable to determine MySQL version
+# from playhouse.mysql_ext import MySQLConnectorDatabase as PeeweeEngine  # todo MySQL connector not installed!
+from playhouse.reflection import generate_models
+
+
+class Client(PeeweeEngine):
+
+    def __init__(self, host='test-stack-mariadb', port=3306, database='vms', username='root', password='root'):
+        """
+        内部访问:
+            host: test-stack-mariadb
+            port: 3306
+        远程访问:
+            host: 118.190.217.96、192.168.20.162
+            port: 17706
+        """
+        super().__init__(database, user=username, password=password, host=host, port=port)
+        tables = generate_models(self)
+        globals().update(tables)
+        self.tables = globals()
+
+        # --- vuls ---
+        # self.vuls = dict()
+        # table = self.tables['vul_detail']
+        # for item in table.select():
+        # 	self.vuls[item.cve_id] = item.__data__
+
+        # def get_vul_by_cve_id(self, cve_id):
+        # 	return self.vuls.get(cve_id, {})
+
+    def get_all(self, table_name):
+        """
+        获取全部数据
+        """
+        table = self.tables[table_name]
+        return table.select()
+
+    def get_one(self, table_name, unique_dict):
+        """
+        单条获取
+        """
+        table = self.tables[table_name]
+        wheres = list()
+        for k, v in unique_dict.items():
+            data = getattr(table, k) == v
+            wheres.append(data)
+        for item in table.select().where(*wheres):
+            return item.__data__
+
+    def execute_sql(self, sql):
+        """
+        执行sql
+        """
+        try:
+            result = db.execute_sql(sql)
+            print(result.__class__.__name__)
+            return result
+        except Exception as exception:
+            print(exception.__class__.__name__)
+            return None
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # db = Client()
+    # db = Client(host='192.168.30.49', port=7033)
+    db = Client(host='127.0.0.1', port=3306, database='ar', username='root', password='20221212!')
+
+    # --- test ---
+    sql = f'''select sum(timestampdiff(second, createtime, updatetime)) from ar.ar_phone_call where status = 1'''
+    result = db.execute_sql(sql)
+    for row in result:
+        print(' --- --- ---- ---')
+        print(row)
+        print(row.__class__.__name__)
+    print(row[0])
+    db.close()
+
+    # --- test ---
+    # items = db.get_all('vul_detail')
+    # for item in items:
+    # 	print(item.cve_id)
+    # 	# print(item.vul_name)
+
+    # --- test ---
+    # out = db.get_one('vul_detail', {'cve_id': 'CVE-2007-1858'})
+    # print(type(out.get('cvss_point')))
+    # print(out.get('cvss_point'))

+ 508 - 0
3rdparty/xclient/xmongo.py

@@ -0,0 +1,508 @@
+# update: 2022-3-30-15
+"""
+$eq 等于
+$ne 不等
+$in []
+db.collection.find({ field: { $not: { $in: [value1, value2, value3] } } })
+$regex 模糊查找
+(>) 大于 - $gt
+(<) 小于 - $lt
+(>=) 大于等于 - $gte
+(<= ) 小于等于 - $lte
+
+
+pip install pymongo==3.11.2 -i https://pypi.tuna.tsinghua.edu.cn/simple
+"""
+from pymongo import MongoClient, TEXT
+from bson.objectid import ObjectId
+
+
+class Client(MongoClient):
+
+    def __init__(self, host='sri-thirdparty-mongo', port=27017, database='vms', username='admin', password='admin'):
+        """
+        内部访问:
+            host: sri-thirdparty-mongo
+            port: 27017
+        远程访问:
+            host: 118.190.217.96、192.168.20.162
+            port: 7030
+        """
+        super().__init__(f'mongodb://{username}:{password}@{host}:{port}')
+        self.db = self[database]
+
+    def run_command(self, command=None, command_type='dict', use_admin=False):
+        """
+        Command:
+            print(self.db.command('serverStatus'))
+            print(self.db.command('dbstats'))
+            print(self['admin'].command('listDatabases', 1))  # 查看数据库信息
+            print(self['admin'].command({'setParameter': 1, 'internalQueryExecMaxBlockingSortBytes': 52428800}))  # 设置排序内存限制
+            print(self['admin'].command({'getParameter': 1, 'internalQueryExecMaxBlockingSortBytes': 1}))  # 查询排序内存限制
+            print(self.db.command('collstats', 'WorkFlowPlugin'))  # 查看表信息
+            print(self.db.command({'collStats': 'WorkFlowPlugin'}))  # 查看表信息
+        Usage:
+            self.run_command(
+                command={'setParameter': 1, 'internalQueryExecMaxBlockingSortBytes': 52428800},
+                use_admin=True
+            )  # 设置排序内存限制
+        """
+        if use_admin and command_type == 'dict':
+            return self['admin'].command(command)
+
+    def get_indexes(self, table_name):
+        """
+        获取索引
+        # return collection.indexes.find()
+        # return collection.list_indexes()
+        """
+        collection = self.db[table_name]
+        return collection.index_information()
+
+    def add_index(self, table_name, index_list):
+        """
+        table_name: Xxx
+        index_list: [("field", 1)]
+        # return collection.create_index([('sec_id', TEXT)], name='search_index_zyq', default_language='english')
+        """
+        collection = self.db[table_name]
+        return collection.create_index(index_list)
+
+    def del_index(self, table_name, index_or_name):
+        """
+        删除索引 https://www.dotnetperls.com/create-index-mongodb
+        table_name: Xxx
+        index_list: [("field", 1)]
+        """
+        collection = self.db[table_name]
+        return collection.drop_index(index_or_name)
+
+    def add(self, table_name, data):
+        """
+        保存数据
+        doc: https://www.cnblogs.com/aademeng/articles/9779271.html
+        """
+        table = self.db[table_name]
+        if type(data) == dict:
+            return str(table.insert_one(data).inserted_id)
+        elif type(data) == list:
+            return [str(i) for i in table.insert_many(data).inserted_ids]
+
+    def filter(self, table_name, condition_dict, sort_field=None):
+        """
+        筛选数据
+        condition_dict: 筛选条件
+        sort_field: [('排序字段', -1)]
+        """
+        table = self.db[table_name]
+        if sort_field:
+            return table.find(condition_dict).sort(sort_field)
+        else:
+            return table.find(condition_dict)
+
+    def filter_by_aggregate(self, table_name, condition=None, sort_dict=None, page=None, size=None):
+        """
+        聚合查询
+        condition: 条件
+        sort_dict: 排序 {'字段': -1}
+        page: 页数
+        size: 条数
+        """
+        table = self.db[table_name]
+        parameters = list()
+        if condition:
+            parameters.append({'$match': condition})
+        if sort_dict:
+            parameters.append({'$sort': sort_dict})
+        if type(page) == int and type(size) == int:
+            skip = 0 if page == 1 else (page - 1) * size
+            parameters.append({'$skip': skip})  # 跳过多少条
+            parameters.append({'$limit': size})
+        return table.aggregate(parameters, allowDiskUse=True)
+
+    def get_count(self, table_name, condition_dict=None):
+        """
+        获取数据数量
+        """
+        table = self.db[table_name]
+        if not condition_dict:
+            condition_dict = {}
+        return table.count_documents(condition_dict)
+
+    def get_all(self, table_name, sort_field=None):
+        """
+        获取数据 / 排序
+        table_name: '表名'
+        sort_field: [('排序字段', -1)]
+        """
+        table = self.db[table_name]
+        if sort_field:
+            return table.find().sort(sort_field)
+        else:
+            return table.find()
+
+    def get_one(self, table_name, unique_dict):
+        """
+        单条获取
+        """
+        table = self.db[table_name]
+        data = table.find_one(unique_dict)
+        if data:
+            return data
+        else:
+            return dict()
+
+    def get_last_one(self, table_name, sort_list=None):
+        """
+        获取最后一条
+        table_name: '集合名称'
+        sort_list: [('_id', -1)]
+        """
+        table = self.db[table_name]
+        if not sort_list:
+            sort_list = [('_id', -1)]
+        return table.find_one(sort=sort_list)
+
+    def get_one_by_id(self, table_name, object_id):
+        """
+        单条获取
+        """
+        table = self.db[table_name]
+        return table.find_one({'_id': ObjectId(str(object_id))})
+
+    def update_one_by_id(self, table_name, object_id, update_dict, need_back=False):
+        """
+        单条数据
+        """
+        table = self.db[table_name]
+        modified_count = table.update_one({'_id': ObjectId(str(object_id))}, {'$set': update_dict}).modified_count
+        if need_back:
+            item = self.get_one_by_id(table_name, object_id)
+            item.pop('_id')
+            item['uuid'] = object_id
+            return item
+        else:
+            return modified_count
+
+    def update_all(self, table_name, condition_dict, update_dict):
+        """
+        批量更新
+        """
+        table = self.db[table_name]
+        return table.update_many(condition_dict, {'$set': update_dict}).matched_count
+
+    def update_one(self, table_name, unique_dict, update_dict, default_dict=None, need_back=False):
+        """
+        # if get:
+        #     update update_one_by_id
+        # else:
+        #     insert default_dict
+        # return: get
+        """
+        data = self.get_one(table_name, unique_dict)
+        if data:
+            object_id = str(data.get('_id'))
+            update_dict.update(unique_dict)
+            self.update_one_by_id(table_name, object_id, update_dict)
+        else:
+            if not default_dict:
+                default_dict = dict()
+            if '_id' in default_dict:
+                del default_dict['_id']
+            default_dict.update(update_dict)
+            default_dict.update(unique_dict)
+            object_id = self.add(table_name, default_dict)
+        if need_back:
+            return self.get_one_by_id(table_name, object_id)
+        else:
+            return object_id
+
+    def remove(self, table_name, condition_dict):
+        """
+        批量删除
+        """
+        table = self.db[table_name]
+        return table.delete_many(condition_dict).deleted_count
+
+    def remove_one_by_id(self, table_name, object_id):
+        """
+        单条删除
+        """
+        table = self.db[table_name]
+        return table.delete_many({'_id': ObjectId(str(object_id))}).deleted_count
+
+    def distinct(self, table_name, field):
+        """
+        去重
+        """
+        table = self.db[table_name]
+        return table.distinct(field)
+
+    def get_all_vague(self, table_name, unique_dict):
+        """
+        模糊查询获取
+        """
+        table = self.db[table_name]
+        return table.find(unique_dict)
+
+    def get_aggregate(self, table_name, condition):
+        """
+        分组聚合查询(by wdj)
+        """
+        table = self.db[table_name]
+        return table.aggregate(condition)
+
+    def get_col_list(self, table_name, condition, _dic):
+        """
+        只返回某一列的数据(by wdj)
+        """
+        table = self.db[table_name]
+        return table.find(condition, _dic)
+
+    # def find_by_aggregate(self, table_name,ip):
+    #     """
+    #     分组查询获取   {"$match":{"ip":ip}   {"$group":{"_id":"","counter":{"$sum":1}}}
+    #     """
+    #     table = self.db[table_name]
+    #     count= table.aggregate([{"$group":{"_id":"$find_plugin","count":{"$sum":"$risk_score"}}}])
+    #     return count
+
+    def deep_filter(self, collection, field_path, filter_type='equal', filter_value=None, return_count=False,
+                    sort_field=None):
+        """
+        field_path: 递进字段名(适用字典类型)
+        filter_type: 筛选类型 contain !contain equal !equal in !in
+        sort_field: [('排序字段', -1)]
+        """
+        if filter_type == 'equal':
+            filter_rule = {'$eq': filter_value}
+        elif filter_type == 'contain':
+            filter_rule = {'$regex': f'^.*{filter_value}.*$'}
+        else:
+            filter_rule = {}
+        condition = {
+            field_path: filter_rule
+        }
+        if return_count:
+            return self.get_count(collection, condition)
+        elif sort_field:
+            return self.filter(collection, condition, sort_field)
+        else:
+            return self.filter(collection, condition)
+
+    def del_collect(self):
+        db_collections = self.db.collection_names()
+        for collection in db_collections:
+            self.db[collection].drop()
+
+    @staticmethod
+    def uuid2oid(uuid):
+        return ObjectId(uuid)
+
+
+if __name__ == '__main__':
+    """
+    sudo python3 -m pip install pymongo
+    sudo python3 /home/server/projects/taiwuict/cscec-8bur-vms/supplement-python/clients/db_mongo.py
+    """
+    # --- init ---
+
+    # mdb = Client(database='vms', host='192.168.0.16', port=7030)
+    # mdb = Client(database='vms', host='192.168.51.242', port=7030)
+    # mdb = Client(database='vms', host='192.168.1.233', port=7030)
+    # mdb = Client(database='vms', host='192.168.0.16', port=7030)
+    # mdb = Client(database='vms', host='10.8.20.115', port=7030)  # 晨鸣大厦
+    # mdb = Client(database='vms', host='192.168.15.195', port=7030)
+    # mdb = Client(database='vms', host='192.168.0.15', port=7030)
+    # mdb = Client(database='vms', host='192.168.101.199', port=7030)  # 章丘项目
+    # mdb = Client(database='vms', host='192.168.15.195', port=7030)  # 第3现场
+    # mdb = Client(database='vms', host='192.168.1.242', port=7030)  # 第4现场
+    # mdb = Client(database='ar', host='58.34.94.176', port=7030)  # 第4现场
+    mdb = Client(host='127.0.0.1', port=47017, database='ar', username='admin', password='admin')
+
+    # --- test ---
+    # data = {
+    #     'username': 'aabbss',
+    #     'password': 'aabbss',
+    #     'role_id': 'aabbss',
+    #     'create_at': 11223344,
+    # }
+    # uuid = mdb.add('User', data)
+    # --- test ---
+    # __condition = {
+    #     'face_name': {'$eq': None},
+    # }
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+    # total = mdb.remove('Face', __condition)
+    # print(total)
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    __condition = {
+        # 'face_name': {'$ne': None},
+        # 'face_name': {'$eq': None},
+        'face_name': {'$regex': '陈忠福'},
+        # 'face_features': {'$eq': b'\x80\x03N.'},
+    }
+    items = mdb.filter('Face', __condition)
+    # for item in items:
+    #     print(item)
+    #     # print(item.keys())
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    # _id = '62cbbefba007e25f12acea9d'
+    # out = mdb.get_one_by_id('Face', _id)
+    # print(out)
+
+    # --- test ---
+    # condition = {
+    #     # 'face_name': {'$eq': None},
+    #     'face_name': {'$ne': None},
+    # }
+    # total = mdb.get_count('Face', condition)
+    # print(total)
+
+    # --- test ---
+    # items = mdb.get_all('Face')
+    # for item in items:
+    #     if 'c7304b' in str(item.get('_id')):
+    #         print(item)
+
+    # --- test ---
+    # __condition = {
+    #     # 'face_feature_info_list': {'$ne': None},
+    #     'face_feature_info_list': {'$eq': None},
+    # }
+    # # items = mdb.filter('Face', __condition)
+    # # for item in items:
+    # #     print(item)
+    # #     break
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+    # total = mdb.remove('Face', __condition)
+    # print(total)
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    # unique_dict = {
+    #     'prc_id': '371324199110201974',
+    # }
+    # item = mdb.get_one('Face', unique_dict)
+    # print(item.get('cscec8_id'))
+
+    # --- test ---
+    # __condition = {
+    #     'face_type_uuid_list': {'$in': ['62a190ca84b2c038bac3519d', 'aa', '62a1a438024e59b2e654467a']}
+    # }
+    # items = mdb.filter('Face', __condition)
+    # for item in items:
+    #     print(item.get('face_name'))
+
+    # --- test ---
+    # __condition = {
+    #     # 'prc_id': {'$ne': None},
+    #     # 'face_name': {'$ne': None},
+    # }
+    # items = mdb.filter('Face', __condition)
+    # for item in items:
+    #     print(item)
+    #     mdb.update_one_by_id('Face', str(item.get('_id')), {'upload_is': None})
+
+    # --- test ---
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    # # _id = '6295942092325efa38758bf1'
+    # # _id = '629591b792325efa38758be2'
+    # # _id = '6295925992325efa38758be5'
+    # _id = '6295915592325efa38758bdf'
+    # # mdb.update_one_by_id('Face', _id, {'prc_id': '370102198802249999'})
+    # # mdb.update_one_by_id('Face', _id, {'prc_id': '370102198802241111'})
+    # mdb.update_one_by_id('Face', _id, {'cscec8_id': 'f6bae517-0a28-429c-b65b-b16548ab2ca4'})
+
+    # --- test ---
+    # import time
+    #
+    # __condition = dict(
+    #     modify_at={'$gte': time.time() - 86400 * 100},
+    #     # modify_at={'$gte': methods.now_ts() - 3600},
+    # )
+    # items = mdb.filter('Face', __condition)
+    # for item in items:
+    #     print(item.keys())
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    # __uuid = '6244068e64481ae85ca38ef9'
+    # item = mdb.get_one_by_id('Face', __uuid)
+    # print(item)
+
+    # --- test ---
+    # _uuid = '6243fa35d169a1bf065f8f02'
+    # item = mdb.get_one_by_id('Face', _uuid)
+    # print(item.get('face_feature_info_list')[0])
+
+    # --- test ---
+    # """
+    # Face: 陌生人脸表
+    # Face.face_name: 姓名
+    # """
+    # __condition = {
+    #     # 'face_name': {'$ne': None},
+    #     # 'face_name': {'$eq': None},
+    #     'face_name': {'$regex': '王'},
+    # }
+    # items = mdb.filter('Face', __condition)
+    # for item in items:
+    #     print(item)
+    #     break
+    # total = mdb.get_count('Face', __condition)
+    # print(total)
+
+    # --- test ---
+    # __condition = {
+    #     'name': {'$eq': '未知类型'}
+    # }
+    # count = mdb.get_count('FaceType', __condition)
+    # print(count)
+
+    # --- test ---
+    # items = mdb.get_all('VisitorInfo')
+    # for item in items:
+    #     _list = item.get('aaabbbccc', [])
+    #     print('_list', type(_list), _list)
+    #     break
+
+    # --- test ---
+    # _condition = {'ipv4': '192.168.30.232'}
+    # items = mdb.filter_by_aggregate('UserCamera', condition=_condition, sort_dict={'create_at': -1}, page=1,
+    #                                 size=10)
+    # count = 0
+    # for item in items:
+    # 	count += 1
+    # print(count)
+
+    # --- test ---
+    # _uuid = '604c22d22043eb79cb425c9c'
+    # out = mdb.remove('AgentDetectionRecord', {'camera_uuid': _uuid})
+    # # out = mdb.get_one('AgentDetectionRecord', {'camera_uuid': _uuid})
+    # print(out)
+
+    # --- test ---
+    # _condition = {
+    #     '_id': {'$in': [ObjectId('605994eb7c29e9dc3887e639')]}
+    # }
+    # items = mdb.filter('AgentDetectionRecord', _condition)
+    # for item in items:
+    #     print(item)
+
+    # --- test ---
+    # out = mdb.get_count('VisitorInfo')
+    # print(out)

+ 19 - 0
3rdparty/xclient/xmqtt-1.py

@@ -0,0 +1,19 @@
+import paho.mqtt.client as mqtt
+
+# MQTT 代理设置
+broker_address = "127.0.0.1"
+broker_port = 41883
+username = "admin"
+password = "public"
+
+# 创建客户端实例
+client = mqtt.Client()
+
+# 设置用户名和密码
+client.username_pw_set(username, password)
+
+# 连接到 MQTT 代理
+client.connect(broker_address, broker_port)
+
+# 可以继续执行 MQTT 操作(发布、订阅等)
+

+ 89 - 0
3rdparty/xclient/xmqtt.py

@@ -0,0 +1,89 @@
+"""
+cd /home/server/repositories/repositories/gitee.com/casperz.py-project/module-py/xclient
+
+pip3 install paho-mqtt==1.6.1
+
+python3 xmqtt-a1.py
+python3 xmqtt-a2.py
+python3 xmqtt.py
+
+
+pip3 install paho-mqtt==1.6.1 -i hhttps://mirror.baidu.com/pypi/simple
+
+
+"""
+import paho.mqtt.client as mqtt
+import time
+import json
+
+
+class Client(mqtt.Client):
+
+    def __init__(self, host='127.0.0.1', port=41883):
+        super().__init__()
+        self.connect(host=host, port=port, keepalive=60)
+        # if subscribe_topic:
+        #     def on_message(client, userdata, message):
+        #         # print(f"#userdata: {userdata}")
+        #         # print(f"#message.payload: {str(message.payload.decode())}")
+        #         print(f"#message.payload: {json.loads(message.payload)}")
+        #         print(f"#message.topic: {message.topic}")
+        #
+        #     self.on_message = on_message
+        #     self.subscribe(subscribe_topic)
+        #     self.loop_forever()
+
+    def start_subscribe_loop(self, decorate_method, subscribe_topic):
+        """启动订阅循环"""
+        self.on_message = decorate_method
+        self.subscribe(subscribe_topic)
+        self.loop_forever()
+
+    def publish_message(self, publish_topic, message):
+        """发布消息"""
+        try:
+            results = self.publish(publish_topic, message)
+            result_code, message_id = results
+            """
+            result_code: 错误码
+            result_code.0: 成功
+            result_code.1: 失败
+            result_code.4: 无法连接到 MQTT 代理
+            result_code.7: QoS错误
+            """
+            print(f"#result_code: {result_code}, #message_id: {message_id}")
+        except Exception as e:
+            print(f'#Exception: {e.__class__.__name__}')
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # c1 = Client(host='127.0.0.1', port=41883)
+    c1 = Client(host='192.168.131.23', port=41883)
+
+    # --- test subscribe ---
+    # def m1(_, __, p3):
+    #     print(f"#message.payload: {json.loads(p3.payload)}")
+    # c1.start_subscribe_loop(decorate_method=m1, subscribe_topic='test/topic')
+
+    # --- test publish ---
+    # while True:
+    #     data = {
+    #         'code': 1001,
+    #         '方向': 6000,
+    #         '刹车': 6000,
+    #         '油门': 8000,
+    #     }
+    #     # c1.publish_message('bg/log', json.dumps(data))
+    #     c1.publish_message('hs/vehicle/state', json.dumps(data))
+    #     time.sleep(3)
+
+    # --- test publish ---
+    while True:
+        data = {
+            'address': "192.168.131.180",
+            'state': 2,
+            'direction': 22,
+        }
+        c1.publish_message('hs/vehicle/state', json.dumps(data))
+        time.sleep(3)

+ 158 - 0
3rdparty/xclient/xmysql.py

@@ -0,0 +1,158 @@
+# update: 2023-11-12-19
+"""
+pip install pymysql==0.9.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
+pip install SQLAlchemy==1.4.30 -i https://pypi.tuna.tsinghua.edu.cn/simple
+
+
+问题:
+    CryptographyDeprecationWarning: Python 3.6 is no longer supported by the Python core team. Therefore, support for it is deprecated in cryptography. The next release of cryptography will remove support for Python 3.6.
+    from cryptography.hazmat.backends import default_backen
+解决:
+    pip3 install cryptography==3.4.8
+
+see: https://blog.csdn.net/weixin_46281427/article/details/122916870
+
+# --- 解决root密码中包含@的问题
+self.mysql_dwd_config = {
+    'drivername': 'mysql+pymysql',
+    'username': 'user_a',
+    'password': 'xxx@#$xxx',
+    'host': 'am-xxxxx.ads.aliyuncs.com',
+    'port': 3306,
+}
+if sqlalchemy.__version__ >= '1.4': #其实大于1.4.15之后,密码里面含有@,就必须以这种方式创建正确正则识别密码的引擎了。
+    self.mysql_engine_url = sqlalchemy.engine.URL.create(**self.mysql_dwd_config)
+    self.mysql_engine_url = self.mysql_engine_url.update_query_dict({'charset': 'utf8mb4'})
+else:
+    # password 含有@
+    self.mysql_engine_url = '{drivername}://{username}:{password}@{host}:{port}/?charset=utf8mb4'.format(**self.mysql_dwd_config)
+self.mysql_dwd_engine = sqlalchemy.create_engine(self.mysql_engine_url)
+"""
+from sqlalchemy import create_engine, inspect, MetaData, Table
+from sqlalchemy.orm import sessionmaker
+
+
+class Client():
+
+    def __init__(self, host='127.0.0.1', port=3306, database='ar', username='root', password='20221212!'):
+        """
+        """
+        # 设置字符集
+        args = 'charset=utf8mb4'
+        # 设置关闭 only_full_group_by 模式
+        args = '&sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
+        self.engine = create_engine(f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}?{args}")
+        # self.engine = create_engine(f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}")
+
+        self.database = database
+        self.inspect = inspect(self.engine)
+
+    def get_all_database(self):
+        """获取全部数据库名称"""
+        return self.inspect.get_schema_names()
+
+    def get_all_table(self, database_name):
+        """获取全部数据表名称"""
+        return self.inspect.get_table_names(schema=database_name)
+
+    def get_all_field(self, table_name):
+        """获取全部字段名称"""
+        fields = self.inspect.get_columns(table_name, schema=self.database)  # 表名,库名
+        return [i.get('name') for i in fields]
+
+    def execute_sql(self, sql):
+        """
+        执行sql
+        """
+        try:
+            result = self.engine.execute(sql).fetchone()[0]
+            # print(result.__class__.__name__)
+            return result
+        except Exception as exception:
+            print(exception.__class__.__name__)
+            return None
+
+    def get_all(self, table_name):
+        """
+        获取全部数据
+        """
+        sql = f"SELECT * FROM {table_name};"
+        try:
+            result = self.engine.execute(sql).fetchall()  # 返回的是列表
+            # print(result.__class__.__name__)
+            return result
+        except Exception as exception:
+            print(exception.__class__.__name__)
+            return None
+
+    def get_one(self, table_name='ar_user', where_key='ACCT', where_val='admin'):
+        """
+        单条获取
+        """
+        sql = f"SELECT * FROM {table_name} WHERE {where_key} = '{where_val}';"
+        try:
+            result = self.engine.execute(sql).fetchone()  # 返回的是元祖
+            # result = self.engine.execute(sql).fetchall()  # 返回的是列表
+            # print(result.__class__.__name__)
+            return result
+        except Exception as exception:
+            print(exception.__class__.__name__)
+            return None
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # db = Client(host='127.0.0.1', port=3306, database='ar', username='root', password='20221212!')
+    db = Client(host='127.0.0.1', port=3306, database='ar', username='root', password='rootroot&123123')
+    # db = Client(host='58.34.94.176', port=8806, database='ar', username='root', password='rootroot&123123')
+
+    # --- test ---
+    # result = db.get_all_field('ar_user')
+    # result = db.get_all('ar_phone_call')
+    result = db.get_one('ar_user', 'USER_ID', '10000LXQ')
+    print(result)
+    print(result[1])
+
+    # --- test ---
+#     sql = f'''select a.USER_ID, a.USER_NAME, a.EMP_NO, a.PHOTO, a.PHONE, c.DEPT_ID, c.DEPT_NAME, e.ROLE_ID, e.ROLE_NAME, a.IS_DISABLED
+#  from ar_user a left JOIN ar_user_dept_rel b ON a.USER_ID = b.USER_ID
+#  left join ar_dept c on c.DEPT_ID = b.DEPT_ID
+#  left join ar_user_role_rel d on a.USER_ID = d.USER_ID
+#  left join ar_role e on d.ROLE_ID = e.ROLE_ID
+#  where a.ACCT = 'admin'
+# '''
+#     sql = f'''select a.USER_ID, a.USER_NAME, a.EMP_NO, a.PHOTO, a.PHONE, c.DEPT_ID, c.DEPT_NAME, e.ROLE_ID, e.ROLE_NAME, a.IS_DISABLED
+#  from AR_USER a left JOIN AR_USER_DEPT_REL b ON a.USER_ID = b.USER_ID
+#  left join AR_DEPT c on c.DEPT_ID = b.DEPT_ID
+#  left join AR_USER_ROLE_REL d on a.USER_ID = d.USER_ID
+#  left join AR_ROLE e on d.ROLE_ID = e.ROLE_ID
+#  where a.ACCT = 'admin'
+# '''
+#     out = db.engine.execute(sql).fetchone()
+#     print(out)
+
+# --- test ---
+# result = db.get_all_field('ar', 'ar_user')
+# print(result)
+
+# --- test ---
+# result = db.get_one()
+# result = db.get_one(table_name='ar_user', where_key='USER_NAME', where_val='陈旭')
+# result = db.get_one(table_name='ar_user', where_key='USER_NAME', where_val='admin')
+# print(result)
+
+# --- test ---
+# sql = f'''select count(1) from ar.ar_phone_call where status in (0, 1)'''
+# result = db.execute_sql(sql)
+# print(result)
+
+# --- test ---
+# items = db.get_all('vul_detail')
+# for item in items:
+# 	print(item.cve_id)
+# 	# print(item.vul_name)
+
+# --- test ---
+# out = db.get_one('vul_detail', {'cve_id': 'CVE-2007-1858'})
+# print(type(out.get('cvss_point')))
+# print(out.get('cvss_point'))

+ 116 - 0
3rdparty/xclient/xqcloudsms.py

@@ -0,0 +1,116 @@
+from qcloudsms_py import SmsSingleSender
+from qcloudsms_py.httpclient import HTTPError
+import time
+
+
+class Client(object):
+
+    def __init__(self):
+        appid = 1400278842  # 短信应用 SDK AppID
+        appkey = '9514696ecf07081a42b4a19bfaf5bf2e'  # 短信应用 SDK AppKey
+        self.ssender = SmsSingleSender(appid, appkey)
+        self.sms_sign = '泽鹿安全'
+
+    def for_add_vul(self, phone_numbers, find_at, address, vul_id, risk):
+        """
+        新增漏洞提醒
+        """
+        template_id = 479975
+        params = [str(find_at), str(address), str(vul_id), str(risk)]
+        for phone_number in phone_numbers:
+            try:
+                result = self.ssender.send_with_param(86, str(phone_number),
+                                                      template_id, params,
+                                                      sign=self.sms_sign,
+                                                      extend='', ext='')
+                print(result)
+            except HTTPError as e:
+                print(e)
+            except Exception as e:
+                print(e)
+
+    def for_close_task(self, phone_numbers, task_id, date_time, username, state):
+        """
+        工单关闭提醒
+        """
+        template_id = 479977
+        params = [str(task_id), str(date_time), str(username), str(state)]
+        for phone_number in phone_numbers:
+            try:
+                result = self.ssender.send_with_param(86, str(phone_number),
+                                                      template_id, params,
+                                                      sign=self.sms_sign,
+                                                      extend='', ext='')
+                print(result)
+            except HTTPError as e:
+                print(e)
+            except Exception as e:
+                print(e)
+
+    def for_remind_task(self, phone_numbers, task_id, residue_time):
+        """
+        工单时限提醒
+        """
+        template_id = 479982
+        params = [str(task_id), str(residue_time)]
+        for phone_number in phone_numbers:
+            try:
+                result = self.ssender.send_with_param(86, str(phone_number),
+                                                      template_id, params,
+                                                      sign=self.sms_sign,
+                                                      extend='', ext='')
+                print(result)
+            except HTTPError as e:
+                print(e)
+            except Exception as e:
+                print(e)
+
+    def for_claim_task(self, phone_numbers, task_id, username, action):
+        """
+        工单状态变化
+        """
+        template_id = 503921
+        year = time.strftime('%Y', time.localtime(int(time.time())))
+        month = time.strftime('%m', time.localtime(int(time.time())))
+        day = time.strftime('%d', time.localtime(int(time.time())))
+        hour = time.strftime('%H', time.localtime(int(time.time())))
+        minute = time.strftime('%M', time.localtime(int(time.time())))
+        params = [str(task_id)[-8:], str(year), str(month), str(day), str(hour), str(minute), str(username)[-8:],
+                  action[-8:]]
+        for phone_number in phone_numbers:
+            try:
+                result = self.ssender.send_with_param(86, str(phone_number),
+                                                      template_id, params,
+                                                      sign=self.sms_sign,
+                                                      extend='', ext='')
+                print(result)
+            except HTTPError as e:
+                print(e)
+            except Exception as e:
+                print(e)
+
+    def for_vul_message(self, phone_numbers, find_at, address):
+        """
+        漏洞提醒
+        """
+        template_id = 630480
+        params = [str(find_at), str(address)]
+        for phone_number in phone_numbers:
+            try:
+                result = self.ssender.send_with_param(86, str(phone_number),
+                                                      template_id, params,
+                                                      sign=self.sms_sign,
+                                                      extend='', ext='')
+                print(result)
+                return result
+            except HTTPError as e:
+                print(e)
+            except Exception as e:
+                print(e)
+
+
+if __name__ == '__main__':
+    # --- init ---
+    client = Client()
+
+    # --- test ---

+ 310 - 0
3rdparty/xclient/xredis.py

@@ -0,0 +1,310 @@
+# update: 2021-7-14-11
+import redis
+import pickle
+
+
+class Client(redis.StrictRedis):
+
+    def __init__(self, host='fra-middleware-redis', port=6379, db=0, password='', channels=[]):
+        """
+        内部访问:
+            host: fra-middleware-redis
+            port: 6379
+        远程访问:
+            host: 118.190.217.96、192.168.20.162
+            port: 7070
+        """
+        self.host = host
+        self.port = port
+        self.db = db
+        super().__init__(host=self.host, port=self.port, db=self.db)
+
+        self.channels = channels
+        if self.channels:
+            """
+            dir(self.pubsub()):
+            ['HEALTH_CHECK_MESSAGE', 'PUBLISH_MESSAGE_TYPES', 'UNSUBSCRIBE_MESSAGE_TYPES', 'channels', 'check_health', 
+            'close', 'connection', 'connection_pool', 'encoder', 'execute_command', 'get_message', 'handle_message', 
+            'health_check_response', 'ignore_subscribe_messages', 'listen', 'on_connect', 'parse_response', 'patterns', 
+            'pending_unsubscribe_channels', 'pending_unsubscribe_patterns', 'ping', 'psubscribe', 'punsubscribe', 
+            'reset', 'run_in_thread', 'shard_hint', 'subscribe', 'subscribed', 'unsubscribe']
+            """
+            self.channel_pubsub = self.pubsub()
+            self.channel_pubsub.psubscribe(channels)
+            # self.channel_listen = self.channel_pubsub.listen()
+
+    def get_config(self):
+        """
+        CONFIG GET|SET: 分别用 config_get 和 config_set 实现。
+        """
+        return self.config_get()
+
+    def set_config(self, name, value):
+        """
+        CONFIG GET|SET: 分别用 config_get 和 config_set 实现。
+        """
+        return self.config_set(name=name, value=value)
+
+    def get_elements(self, key):
+        """获取列表元素"""
+        return self.lrange(key, 0, -1)
+
+    def get_elements_length(self, key):
+        """获取列表长度"""
+        return self.llen(key)
+
+    def pop_element(self, key, position='RightEnd'):
+        """
+        弹出元素
+        position: 出队位置 LeftEnd 左端 RightEnd 右端
+        """
+        _dict = {
+            'LeftEnd': 'lpop',
+            'RightEnd': 'rpop',
+        }
+        func = getattr(self, _dict.get(position))
+        pickle_data = func(key)
+        element = pickle.loads(pickle_data) if pickle_data else None
+        return element
+
+    def add_element(self, key, element, position='RightEnd'):
+        """
+        增加元素
+        position: 入队位置 LeftEnd 左端 RightEnd 右端
+        """
+        _dict = {
+            'LeftEnd': 'lpush',
+            'RightEnd': 'rpush',
+        }
+        func = getattr(self, _dict.get(position))
+        pickle_data = pickle.dumps(element)
+        elements_length = func(key, pickle_data)
+        return elements_length
+
+    def set_one(self, key, data, expire_time=None):
+        """压缩存储"""
+        pickle_data = pickle.dumps(data)
+        return self.set(key, pickle_data, ex=expire_time)
+
+    def get_one(self, key):
+        """解压获取"""
+        pickle_data = self.get(key)
+        return pickle.loads(pickle_data) if pickle_data else None
+
+    def push_one(self, channel, data):
+        """
+        推送一条
+        """
+        return self.publish(channel, pickle.dumps(data))
+
+    def pull_one(self, channel):
+        """
+        拉取一条
+        msg = self.channel_pubsub.parse_response(block=False, timeout=60)
+        msg = self.channel_pubsub.parse_response(block=False)
+            [b'pmessage', b'cctv1', b'cctv1', b'3']
+        msg = self.channel_pubsub.get_message()
+            {'type': 'pmessage', 'pattern': b'cctv1', 'channel': b'cctv1', 'data': b'asdfasdfasdf'}
+        """
+        message = self.channel_pubsub.get_message()
+        channel = channel.encode()
+        if message and message.get('pattern') and message.get('channel') == channel:
+            return pickle.loads(message.get('data'))
+
+    def pull_one_by_channel(self, channel):
+        """
+        拉取一条
+        """
+        message = self.channel_pubsub.get_message()
+        channel = channel.encode()
+        if message and message.get('pattern') and message.get('channel') == channel:
+            return pickle.loads(message.get('data'))
+
+    def pull_one_by_channels(self):
+        """
+        拉取一条
+        """
+        message = self.channel_pubsub.get_message()
+        channels = [i.encode() for i in self.channels]
+        if message and message.get('pattern') and message.get('channel') in channels:
+            return pickle.loads(message.get('data'))
+
+    def pull_one_by_channels_v2(self, channels):
+        """
+        拉取一条
+        """
+        message = self.channel_pubsub.get_message()
+        channels = [i.encode() for i in channels]
+        if message and message.get('pattern') and message.get('channel') in channels:
+            return pickle.loads(message.get('data'))
+
+    def pull_one_by_channels_v3(self, channels):
+        """
+        拉取一条(性能不佳)
+        """
+        self.channel_pubsub.psubscribe(channels)
+        message = self.channel_pubsub.get_message()
+        channels = [i.encode() for i in channels]
+        if message and message.get('pattern') and message.get('channel') in channels:
+            return pickle.loads(message.get('data'))
+
+    def print_all(self):
+        """打印"""
+        for key in self.keys():
+            key_type = self.type(key)
+
+            # if key_type != b"stream":
+            #     continue
+
+            if key_type == b'string':
+                value = self.get(key)
+            elif key_type == b'list':
+                value = self.lrange(key, 0, -1)
+            elif key_type == b'set':
+                value = self.smembers(key)
+            elif key_type == b'zset':
+                value = self.zrange(key, 0, -1)
+            elif key_type == b"hash":
+                value = self.hgetall(key)
+            elif key_type == b"stream":
+                value = self.xread({key: b"0-0"})
+            else:
+                value = None
+
+            print(f"type: {key_type} | key: {key}")
+            # print(f"type: {key_type} | key: {key} | value: {value}")
+
+    def filter_keys(self):
+        re_list = []
+        for key in self.keys():
+            if self.type(key) != b'string':
+                continue
+            if b'warn' not in key:
+                continue
+            re_list.append(key)
+        return re_list
+
+    def test(self):
+        while True:
+            msg = self.pull()
+            if msg:
+                print(msg)
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # db0 = Client(host='192.168.30.49', port=7070, channels=['cctv1', 'cctv2'])
+    db0 = Client(host='192.168.30.59', port=7070)
+    # db0 = Client(host='192.168.1.190', port=7070)
+    # db0 = Client(host='fra-middleware-redis', port=6379)
+
+    # --- test ---
+    out = db0.get_elements_length('tasklist1')
+    print(out)
+
+    # --- test --
+    # db0.set_config('client-output-buffer-limit', 'normal 0 0 0 slave 268435456 67108864 60 pubsub 0 0 0')
+    # out = db0.get_config()
+    # print(out)
+
+    # --- test ---
+    # db0.push_one('cctv1', 'asdfasdfasdf')
+
+    # --- test ---
+    # db0.set_object('compare_face_task', [1, 2, 3])
+
+    # --- test ---
+    # out = db0.get_object('compare_face_task')
+    # print(out)
+
+    # --- test ---
+    # db0.hset(name='test001', key='test002', value='test003')
+    # out = db0.hgetall(name='test001')
+    # print(out)
+
+    # --- test ---
+    # db0.print_all()
+
+    # --- test ---
+    # db0.flushdb()
+
+    # --- test ---
+    # import pickle
+    # out = db0.get('real_cam')
+    # out = pickle.loads(out)
+    # print(out)
+
+    # --- test ---
+    # import pickle
+    # out = db0.get('webcam')
+    # out = pickle.loads(out)
+    # print(out)
+
+    # --- test ---
+    # import pickle
+    # config = {
+    #     'aass': 12,
+    #     '1': {
+    #        '2': {
+    #        }
+    #     }
+    # }
+    #
+    # config = pickle.dumps(config)
+    # db0.set('webcam', config, ex=1000)
+    # out = db0.get('webcam')
+    # out = pickle.loads(out)
+    # print(out)
+
+    # --- test ---
+    # out = db0.filter_keys()
+    # print(out)
+    # out = db0.get(out[0])
+    # print(out)
+
+    # --- test ---
+    # db0.delete('warn:192.168.1.68:1604928475')
+    # db0.delete('warn:192.168.1.68:1604928695')
+    # db0.delete('warn:192.168.1.68:1604928479')
+    # db0.delete('warn:192.168.1.68:1604928680')
+    # db0.delete('warn:192.168.1.68:1604928491')
+    # db0.print_all()
+
+    # --- test ---
+    # import pickle
+    # data = pickle.dumps(0)
+    # db0.set('warn:192.168.1.68:<last_at>', data, ex=1000*86400)
+    # out = db0.get(f'warn:192.168.1.68:<last_at>')
+    # print(out)
+
+    # --- test ---
+    # import pickle
+    # data = {
+    #     'last_at': 112233,
+    #     'image': pickle.dumps({}),
+    # }
+    # db0.hmset(name='192.168.1.68:admin:HuaWei123:<find_at>', mapping=data)
+    # out = db0.hmget(name='192.168.1.68:admin:HuaWei123:<find_at>', keys='last_at')
+    # print(out)
+    # out = db0.hget(name='192.168.1.68:admin:HuaWei123:<find_at>', key='last_at')
+    # print(out)
+
+    # --- test ---
+    # db0.set('food', b'mutton', ex=1000)
+    # print(type(db0.get('food')))
+    # print(repr(db0.get('food')))
+    # db0.print_all()
+
+    # --- test ---
+    # data = db0.get('192.168.1.68:admin:HuaWei123')
+    # import numpy as np
+    # data = np.frombuffer(data)
+    # print(repr(data))
+
+    # --- test ---
+    # data = db0.get('192.168.1.68:admin:HuaWei123')
+    # import pickle
+    # data = pickle.loads(data)
+    # import cv2
+    # cv2.imshow('data', data)
+    # cv2.waitKey(0)

+ 42 - 0
3rdparty/xclient/xsmtp.py

@@ -0,0 +1,42 @@
+# update: 2021-1-27-10
+import smtplib
+from email.mime.text import MIMEText
+from email.header import Header
+
+
+class Client(object):
+
+    def __init__(self, username='zhangyouqian@xxx.com', password='DEVdev123',
+                 smtp_host='smtp.exmail.qq.com',  smtp_port=465):
+        self.username = username
+        self.password = password
+        self.smtp_host = smtp_host
+        self.smtp_port = smtp_port
+
+    def send_mails(self, addrs=['zhangyouqian@secdeer.com'],
+                   title='test-0404', text='test', addressee='尊敬的用户', addresser='xxx'):
+
+        # addrs.append('daien@secdeer.com')
+        # addrs.append('lijiaming@secdeer.com')
+        message = MIMEText(text, 'plain', 'utf-8')
+        message['Subject'] = Header(title, 'utf-8')
+        message['From'] = Header(addresser, 'utf-8')
+        message['To'] = Header(addressee, 'utf-8')
+
+        try:
+            server = smtplib.SMTP_SSL(host=self.smtp_host, port=self.smtp_port)
+            server.login(self.username, self.password)
+            server.sendmail(from_addr=self.username, to_addrs=addrs, msg=message.as_string())
+            print('邮件发送成功')
+            return True
+        except Exception as e:
+            import traceback
+            print('Error: %s' % e.__class__.__name__)
+            print('Message: %s' % str(traceback.format_exc()))
+            print('邮件发送失败')
+            return False
+
+
+if __name__ == '__main__':
+    s = Client()
+    s.send_mails(addrs=['xuzhongyiqiang@xxx.com'])

+ 76 - 0
3rdparty/xclient/xsmtp_zl.py

@@ -0,0 +1,76 @@
+import smtplib
+from email.mime.text import MIMEText
+from email.header import Header
+from email.mime.multipart import MIMEMultipart
+from email.mime.application import MIMEApplication
+
+
+class Client(object):
+
+    def __init__(self):
+        self.username = 'xuzhongyiqiang@secdeer.com'
+        self.password = 'YIqiang521..'
+        self.ip = 'smtp.exmail.qq.com'
+        self.port = 45
+
+    def send_email(self, body, send_part2=None, smtp_server="smtp.exmail.qq.com"):
+        """
+        :param body:文件内容
+        :param send_part2: 附件地址
+        :param smtp_server: smtp地址
+        :return:
+        """
+        # 1.发件人、授权码,收件人信息
+        from_addr = "xuzhongyiqiang@secdeer.com"
+        pwd = "YIqiang521.."
+        to_addr = "1092364772@qq.com"
+
+        # 2.创建实例对象,设置主题等信息
+        msg = MIMEMultipart()
+        msg["Subject"] = "清零安全日报"
+        msg["From"] = from_addr
+        msg["To"] = to_addr
+
+        # 邮件内容(按每个部分)
+        part1 = MIMEText(body, "html", 'utf-8')
+        msg.attach(part1)
+        print(part1)
+
+        # send_part2 :附件地址
+        if send_part2:
+            part2 = MIMEApplication(open(send_part2, 'rb').read())
+            part2.add_header('Content-Disposition', 'attachment', filename=send_part2)
+            msg.attach(part2)
+
+        # 3.连接smtp服务器,登录服务器并发送文本
+        smtp_server = smtp_server
+        server = smtplib.SMTP(smtp_server, 25)
+        server.login(from_addr, pwd)
+        server.sendmail(from_addr, to_addr, msg.as_string())  # as_string()把MIMEText变成一个str
+        server.close()
+
+    def send_report_mail(self, addrs=['zhangyouqian@secdeer.com'],
+                         title='爬虫报告', text='爬虫报告', addressee='尊敬的用户', addresser='泽廘安全'):
+
+        addrs.append('daien@secdeer.com')
+        # addrs.append('lijiaming@secdeer.com')
+        message = MIMEText(text, 'plain', 'utf-8')
+        message['Subject'] = Header(title, 'utf-8')
+        message['From'] = Header(addresser, 'utf-8')
+        message['To'] = Header(addressee, 'utf-8')
+
+        try:
+            server = smtplib.SMTP_SSL(self.ip, self.port)
+            server.login(self.username, self.password)
+            server.sendmail(from_addr=self.username, to_addrs=addrs, msg=message.as_string())
+            print('邮件发送成功')
+        except Exception as e:
+            import traceback
+            print('Error: %s' % e.__class__.__name__)
+            print('Message: %s' % str(traceback.format_exc()))
+            print('邮件发送失败')
+
+
+if __name__ == '__main__':
+    s = Client()
+    s.send_report_mail(addrs=['zhangyouqian@secdeer.com'])

+ 199 - 0
3rdparty/xclient/xssh.py

@@ -0,0 +1,199 @@
+# update: 2021-3-26-13
+"""
+https://www.jianshu.com/p/512a5641b501
+
+# --- 上传脚本 ---
+sftp = s.open_sftp()
+sftp.put('../test.sh', '/data/test.sh')
+sftp.close()
+
+# --- 执行脚本并删除 ---
+
+docker exec zelu_8891 bash -c "python /home/server/projects/python-admin/zelu/libraries/protocol_l4/ssh/client.py"
+"""
+import paramiko
+
+
+def test():
+    host_ip = '192.168.30.13'
+    username = 'fish'
+    password = 'admin'
+
+    client = paramiko.SSHClient()
+    try:
+
+        # --- password login ---
+        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        client.connect(hostname=host_ip, port=22, username=username, password=password)
+
+        # --- test ---
+        stdin, stdout, stderr = client.exec_command('ls -all /home')
+        # stdin, stdout, stderr = client.exec_command('ls /home/ShellScripts')
+        print('--- --- --- ---')
+        print(stdout.read().decode('utf-8'))
+
+    except Exception as e:
+        print(e)
+    finally:
+        client.close()
+
+
+class Client(paramiko.SSHClient):
+
+    def __init__(self, ipv4='172.18.0.1', port=22, username='fish', password='admin'):
+
+        super().__init__()
+        self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        self.connect(hostname=ipv4, port=port, username=username, password=password)
+
+    def run_script(self, script_path, host_ip, username, password, command='sh /root/1.sh && cat /home/out.txt'):
+        """执行上传脚本"""
+        # script_path = '/home/server/projects/user-dashboard/operation/builtin_script/linux基线检查脚本.sh'
+        # script_path = r'D:\share\gitee\secdeer.user-dashboard-online\operation\builtin_script\linux基线检查脚本.sh'
+        # host_ip = '47.104.160.37'
+        # username = 'root'
+        # password = 'Secdeer_01!'
+        self.connect(hostname=host_ip, port=22, username=username, password=password)
+
+        sftp = self.open_sftp()
+        sftp.put(script_path, '/root/1.sh')
+        sftp.close()
+
+        # stdin, stdout, stderr = self.exec_command('ls -all /home')
+        # stdin, stdout, stderr = self.exec_command('sh /root/1.sh')
+        # stdin, stdout, stderr = self.exec_command('cat /home/out.txt')
+        stdin, stdout, stderr = self.exec_command(command)
+        output = stdout.read().decode('utf-8')
+        print(output)
+        print('--- --- --- ---')
+        return output
+
+    def run_command(self, command):
+        """执行命令"""
+        try:
+            stdin, stdout, stderr = self.exec_command(command)
+            output = stdout.read().decode('utf-8')
+            return output
+        except Exception as exception:
+            import traceback
+            print(f"SSHClient.run_command.exception: {exception.__class__.__name__}")
+            print(f"SSHClient.run_command.traceback: {traceback.format_exc()}")
+
+    # finally:
+    # 	self.close()
+
+    def run_sudo_command(self, command, password):
+        """执行命令"""
+        try:
+            stdin, stdout, stderr = self.exec_command(f"echo \"{password}\" | sudo -S {command}")
+            output = stdout.read().decode('utf-8')
+            return output
+        except Exception as exception:
+            import traceback
+            print(f"SSHClient.run_sudo_command.exception: {exception.__class__.__name__}")
+            print(f"SSHClient.run_sudo_command.traceback: {traceback.format_exc()}")
+
+    # finally:
+    # 	self.close()
+
+    def get_disk_utilization_rate(self, path='/'):
+        """获取根目录磁盘所用空间占比"""
+        try:
+            stdin, stdout, stderr = self.exec_command("df -h")
+            output = stdout.read().decode('utf-8')
+
+            # --- get utilization_rate by '/' ---
+            utilization_rate = str()
+            for row in output.split('\n'):
+                if not row:
+                    continue
+                if row[-1] != '/':
+                    continue
+                for val in row.split(' '):
+                    if '%' in val:
+                        utilization_rate = val
+                        break
+
+            # --- transform ---
+            if '%' in utilization_rate:
+                utilization_rate = int(utilization_rate.strip('%'))
+            else:
+                utilization_rate = 0
+            return utilization_rate
+
+        except Exception as exception:
+            import traceback
+            print(f"SSHClient.show_root_disk.exception: {exception.__class__.__name__}")
+            print(f"SSHClient.show_root_disk.traceback: {traceback.format_exc()}")
+
+    def close(self):
+        self.close()
+
+
+if __name__ == '__main__':
+    # --- init ---
+    # ssh = Client('192.168.30.13', 22, 'fish', 'admin')
+    # ssh = Client('172.18.0.1', 22, 'fish', 'admin')
+    # ssh = Client('192.168.1.45', 22, 'server', 'server')
+    # ssh = Client('192.168.30.13', 22, 'server', 'server')
+    ssh = Client('10.10.10.89', 22, 'nvidia', '123456')
+
+    # --- test ---
+    out = ssh.get_disk_utilization_rate()
+    print(out)
+
+    # --- test ---
+    # out = ssh.run_command('date +%Y-%m-%d-%H-%M-%S')
+    # date +"%Y-%m-%d %H:%M:%S"
+    # print(out)
+
+    # --- test ---
+    # ssh.run_sudo_command('echo -e "\n#x1" >> /etc/network/interfaces', 'admin')
+    # ssh.run_sudo_command('echo -e "\n#x1" >> /etc/network/interfaces', 'server')
+    # ssh.run_command('echo -e "\n#x2" >> /etc/network/interfaces')
+    # ssh.run_command('sudo echo -e "\n#x3" >> /etc/network/interfaces')
+    # out = ssh.run_command("sudo chmod 777 /etc/network/interfaces")
+    # out = ssh.run_command("cat /etc/network/interfaces")
+    # print(out)
+
+    # --- test ---
+    # out = ssh.run_command("ifconfig")
+    # for row in out.split('\n\n'):
+    # 	if row[:4] != 'eth0':
+    # 		continue
+    # 	for one in row.split('\n'):
+    # 		one = one.strip()
+    # 		if one[:4] != 'inet':
+    # 			continue
+    # 		if one[:5] == 'inet6':
+    # 			continue
+    # 		one = [i for i in one.split(' ') if i]
+    # 		ipv4, netmask = one[1], one[3]
+    # 		print(ipv4, netmask)
+
+    # --- test ---
+    # out = ssh.run_command('ls /home/server/images')
+    # out = ssh.run_command('rm -rf /home/server/images/*')
+    # out = ssh.run_sudo_command('rm -rf /home/server/images/*', 'admin')
+    # print(out)
+
+    # --- test ---
+    # out0 = ssh.run_sudo_command('chmod 777 /etc/network/interfaces', 'admin')
+    # out1 = ssh.run_command('echo -e "\n#x2" >> /etc/network/interfaces')
+    # out2 = ssh.run_command("cat /etc/network/interfaces")
+    # print(out2)
+
+    # --- test ---
+    # out = c.run_command("ls -all /home")
+    # out = c.run_command("df -h | grep 234G | awk \'{print $5}\'")
+    # out = c.run_command("df -h | grep \"/dev/vda1\" | awk \'{print $5}\'")
+    # out = c.run_command("df -h ")
+    # print(out)
+
+    # --- test ---
+    # out = c.run_script(1, 2, 3, 4)
+    # print(repr(out))
+
+    # --- test ---
+    # out = c.run_script('/home/UserFiles/hello.sh', '118.190.217.96', 'root', 'Secdeer_01!', 'sh /root/1.sh')
+    # print(repr(out))

+ 31 - 0
3rdparty/xclient/xtcp.py

@@ -0,0 +1,31 @@
+"""
+python3 /home/server/repositories/repositories/sri-project.demo-py/3rdparty/xclient/xtcp.py
+"""
+import asyncio
+import struct
+
+async def send_message(reader, writer, values):
+    data = struct.pack('!hh', *values)
+    writer.write(data)
+    await writer.drain()
+    print(f"Sent values: {values}")
+
+    size = struct.calcsize('!hh')
+    response = await reader.read(size)
+    if response:
+        received_values = struct.unpack('!hh', response)
+        print(f"Received response: {received_values}")
+
+async def start_client(host='127.0.0.1', port=20917, messages=[(100, 200)]):
+    reader, writer = await asyncio.open_connection(host, port)
+
+    try:
+        for values in messages:
+            await send_message(reader, writer, values)
+    finally:
+        writer.close()
+        await writer.wait_closed()
+
+if __name__ == "__main__":
+    messages = [(100, 200), (300, 400), (500, 600)]  # 示例消息列表
+    asyncio.run(start_client(messages=messages))

+ 48 - 0
3rdparty/xclient/xudp.py

@@ -0,0 +1,48 @@
+"""
+python3 /home/server/repositories/repositories/sri-project.demo-py/3rdparty/xclient/xudp.py
+"""
+import asyncio
+import struct
+
+
+class CustomProtocol:
+    def __init__(self):
+        self.data_received = asyncio.Future()
+
+    def connection_made(self, transport):
+        self.transport = transport
+
+    def datagram_received(self, data, addr):
+        self.data_received.set_result((data, addr))
+
+    def connection_lost(self, exc):
+        pass
+
+    async def send_message(self, values, addr=('127.0.0.1', 20917)):
+        data = struct.pack('!hh', *values)
+        self.transport.sendto(data, addr)
+        print(f"Sent values: {values}")
+
+        response, _ = await self.data_received
+        if response:
+            received_values = struct.unpack('!hh', response)
+            print(f"Received response: {received_values}")
+
+
+async def start_client(messages=[(100, 200)]):
+    protocol = CustomProtocol()
+    transport, _ = await asyncio.get_running_loop().create_datagram_endpoint(
+        lambda: protocol,
+        remote_addr=('127.0.0.1', 20917)
+    )
+
+    try:
+        for values in messages:
+            await protocol.send_message(values)
+    finally:
+        transport.close()
+
+
+if __name__ == "__main__":
+    messages = [(100, 200), (300, 400), (500, 600)]  # 示例消息列表
+    asyncio.run(start_client(messages=messages))

+ 39 - 0
3rdparty/xdecorator.py

@@ -0,0 +1,39 @@
+import xlib as methods
+import threading
+
+
+def single_method(_func):
+    """频率控制"""
+    cache = {}
+
+    def decorated_func(*args, **kwargs):
+        func_name = _func.__name__
+
+        if func_name not in cache:
+            cache[func_name] = 'NoRunning'
+
+        if cache.get(func_name) == 'NoRunning':
+            cache[func_name] = 'IsRunning'
+            try:
+                results = _func(*args, **kwargs)
+                cache[func_name] = 'NoRunning'
+                return results
+            except Exception as e:
+                cache[func_name] = 'NoRunning'
+                methods.debug_log(f"@single_method", f"function '{func_name}' is {e.__class__.__name__}!")
+                return dict(code=-1)
+        else:
+            methods.debug_log(f"@single_method", f"function {repr(func_name)} is not finished yet!")
+            return dict(code=1)
+
+    return decorated_func
+
+
+def new_thread(func):
+    """启用线程方式执行"""
+
+    def wrapper(*args, **kwargs):
+        t = threading.Thread(target=func, args=args, kwargs=kwargs)
+        t.start()
+
+    return wrapper

+ 306 - 0
3rdparty/xengine/cv_face_recognition/engine.py

@@ -0,0 +1,306 @@
+# note: https://github.com/SthPhoenix/InsightFace-REST
+"""
+    测试结果:
+        使用python版本的onnxruntime,推理onnx模型,推理一次时间为4751.71ms
+
+    本测试用例适用于onnxruntime-gpu 1.10.0版本
+
+    100帧fp16处理时间
+        TensorrtExecutionProvider: 1464ms
+
+    24帧fp16处理时间
+        TensorrtExecutionProvider: 358.92ms
+
+    1帧fp16处理时间
+        TensorrtExecutionProvider: 49ms
+
+    100帧处理时间
+        TensorrtExecutionProvider: 3727ms
+        CUDAExecutionProvider: 5169ms
+        CPUExecutionProvider: 68953ms
+
+    24帧处理时间
+        TensorrtExecutionProvider: 796ms
+        CUDAExecutionProvider: 1206ms
+        CPUExecutionProvider: 16591ms
+
+    1帧测试
+        TensorrtExecutionProvider: 85ms
+        CUDAExecutionProvider: 88ms
+        CPUExecutionProvider: 738ms
+"""
+from numpy.linalg import norm
+
+import onnxruntime
+import numpy as np
+import cv2
+import logging
+import binascii
+
+import sys
+import importlib
+
+sys.path.append('/home/server/projects/taiwuict/cscec-8bur-vms/supplement-python')
+methods = importlib.import_module(f"libraries.base_original")
+numpy_method = importlib.import_module(f"libraries.base_external.data_by_numpy")
+
+
+class FaceRecognitionEngine:
+    def __init__(self):
+        model_path = '/home/server/resources/DockerDependencies/2022/target/AlgorithmModels/FaceRecognition-arcface/'
+        model_path += 'arcface_r100_v1.onnx'
+        # model_path += 'MFR_glintr100.onnx'
+
+        # --- debug mode ---
+        # self.onnx_session = onnxruntime.InferenceSession(model_path, providers=['TensorrtExecutionProvider'])
+        self.onnx_session = onnxruntime.InferenceSession(model_path, providers=['CUDAExecutionProvider'])  # for test
+        # self.onnx_session = onnxruntime.InferenceSession(model_path, providers=['CPUExecutionProvider'])
+        # self.onnx_session.set_providers(['CUDAExecutionProvider'], [{'device_id': 1}])  # 指定GPU
+
+        # --- release mode ---
+        # providers = [
+        #     ('TensorrtExecutionProvider', {
+        #         # 'device_id': 1,
+        #         # 'trt_max_workspace_size': 2147483648,
+        #         'trt_fp16_enable': True,
+        #         # 'trt_int8_enable': True,
+        #         # 'trt_engine_cache_enable': True,
+        #         # 'trt_engine_cache_path': '',
+        #     }),
+        # ]
+        # self.onnx_session = onnxruntime.InferenceSession(model_path, providers=providers)
+
+        self.outputs = [e.name for e in self.onnx_session.get_outputs()]
+
+    def prepare(self, **kwargs):
+        """模型初始化"""
+        logging.info("Warming up ArcFace ONNX Runtime engine...")
+        self.onnx_session.run(output_names=self.outputs,
+                              input_feed={
+                                  self.onnx_session.get_inputs()[0].name: [np.zeros((3, 112, 112), np.float32)]})
+
+    @staticmethod
+    def normalize(embedding):
+        """特征数据格式化"""
+        embedding_norm = norm(embedding)
+        normed_embedding = embedding / embedding_norm
+        return normed_embedding
+
+    def get_face_features_normalization_by_image_array(self, image_array):
+        """获取特征"""
+
+        # --- debug ---
+        # methods.debug_log('FaceRecognitionEngine', f"m-92: size: {image_array.shape}")
+
+        # --- check todo 连乘得到像素值,旨在过滤低于112的尺寸图片 ---
+        # if np.prod(image_array.shape) < np.prod((112, 112, 3)):
+        #     return None
+
+        # --- check ---
+        if image_array is None:
+            return None
+
+        # --- check ---
+        if image_array.shape != (112, 112, 3):
+            # methods.debug_log('FaceRecognitionEngine', f"m-96: image resize before is {image_array.shape}")
+            image_array = cv2.resize(image_array, (112, 112))
+
+        if not isinstance(image_array, list):
+            image_array = [image_array]
+
+        for i, img in enumerate(image_array):
+            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+            img = np.transpose(img, (2, 0, 1))
+            image_array[i] = img.astype(np.float32)
+
+        image_array = np.stack(image_array)
+        net_out = self.onnx_session.run(self.outputs, {self.onnx_session.get_inputs()[0].name: image_array})
+        return self.normalize(net_out[0][0])
+
+    @staticmethod
+    def image_bytes_to_image_array(image_bytes, mode='RGB'):
+        """
+        数据格式转换
+        """
+        _image = numpy_method.bytes_to_array_v2(image_bytes)
+        _image = cv2.imdecode(_image, cv2.IMREAD_COLOR)
+        return _image
+
+    def get_face_features_normalization_by_image_bytes(self, image_bytes):
+        """获取特征"""
+        # --- bytes to array ---
+        image_array = self.image_bytes_to_image_array(image_bytes)
+
+        # --- check todo 低于112的尺寸,则过滤掉 ---
+        # if np.prod(image_array.shape) < np.prod((112, 112, 3)):
+        #     return None
+
+        # --- check size --- todo 如果是4通道,考虑通过cvtColor转3通道
+        if image_array.shape != (112, 112, 3):
+            image_array = cv2.resize(image_array, (112, 112))
+
+        if not isinstance(image_array, list):
+            image_array = [image_array]
+
+        for i, img in enumerate(image_array):
+            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+            img = np.transpose(img, (2, 0, 1))
+            image_array[i] = img.astype(np.float32)
+
+        image_array = np.stack(image_array)
+        net_out = self.onnx_session.run(self.outputs, {self.onnx_session.get_inputs()[0].name: image_array})
+        return self.normalize(net_out[0][0])
+
+    @staticmethod
+    def compare_faces_by_normalization(input_normalization, specimen_normalization):
+        """
+        计算相似度(使用格式化数据)
+        """
+        _sim = (1.0 + np.dot(input_normalization, specimen_normalization)) / 2.0
+        return _sim
+
+    def search_face(self, face_features_normalization, face_dict):
+        """
+        寻找近似人脸(face_dict为一对一)
+        """
+
+        # --- get face ---
+        best_face_uuid, best_face_dist = None, None
+        for face_uuid, face_features in face_dict.items():
+
+            dist = self.compare_faces_by_normalization(face_features_normalization, face_features)
+
+            # --- check --- 高相似度,直接返回 (85%以上基本就判定为同一个人了)
+            # if dist > 0.85:
+            #     methods.debug_log('FaceRecognitionEngine', f"m-162: best dist {dist} | {type(dist)}")
+            #     return face_uuid, dist
+
+            # --- check --- 低相似度,直接过滤 (一般低于71%就不是一个人了)
+            if dist > 0.71 and not best_face_dist:
+                best_face_dist = dist
+                best_face_uuid = face_uuid
+
+            if best_face_dist and dist > best_face_dist:
+                best_face_dist = dist
+                best_face_uuid = face_uuid
+
+        # methods.debug_log('FaceRecognitionEngine', f"m-178: best dist {best_face_dist}, face uuid is{best_face_uuid}")
+        return best_face_uuid, best_face_dist
+
+    # def search_face_v2(self, face_features_normalization, face_dict, filter_dist=0.745):
+    # def search_face_v2(self, face_features_normalization, face_dict, filter_dist=0.7):
+    def search_face_v2(self, face_features_normalization, face_dict, filter_dist=0.72):
+        """
+        寻找近似人脸(face_dict为一对多)
+        filter_dist: 相似度判定值
+        """
+
+        # --- get face ---
+        best_face_uuid, best_face_dist, best_face_image_path = None, 0.0, None
+        for face_uuid, info_list in face_dict.items():
+
+            for info in info_list:
+
+                face_features, image_path = info
+                dist = self.compare_faces_by_normalization(face_features_normalization, face_features)
+
+                # --- check --- 高相似度,直接返回 (85%以上基本就判定为同一个人了)
+                # if dist > 0.85:
+                #     methods.debug_log('FaceRecognitionEngine', f"m-162: best dist {dist} | {type(dist)}")
+                #     return face_uuid, dist
+
+                # --- check --- 低相似度,直接过滤 (一般低于72%就不是一个人了)
+                if dist > filter_dist and not best_face_dist:
+                    best_face_dist = dist
+                    best_face_uuid = face_uuid
+                    best_face_image_path = image_path
+
+                # --- update ---
+                if best_face_dist and dist > best_face_dist:
+                    best_face_dist = dist
+                    best_face_uuid = face_uuid
+                    best_face_image_path = image_path
+
+        # methods.debug_log('FaceRecognitionEngine', f"m-206: best dist {best_face_dist}, face uuid is{best_face_uuid}")
+        return best_face_uuid, best_face_dist, best_face_image_path
+
+    def search_face_top3(self, face_features_normalization, face_dict):
+        """
+        寻找近似人脸TOP3
+        """
+
+        # --- define ---
+        d1 = list()  # [{rate: <rate>, uuid: <uuid>}]
+
+        # --- get face ---
+        for face_uuid, face_features in face_dict.items():
+            rate = self.compare_faces_by_normalization(face_features_normalization, face_features)
+            d1.append({'rate': rate, 'uuid': face_uuid})
+            d1[rate] = face_uuid
+
+        # --- check ---
+        d2 = sorted(d1, key=lambda d: d['rate'], reverse=True)[:3]
+        for _ in range(3 - len(d2)):
+            d2.append({'rate': None, 'uuid': None})
+        return d2[0], d2[1], d2[2]
+
+    @staticmethod
+    def clip_image(image_array, left, top, width, height):
+        """剪裁图像"""
+
+        # --- debug ---
+        # methods.debug_log('FaceRecognitionEngine', f"m-185: size: {image_array.shape}")
+        # methods.debug_log('FaceRecognitionEngine',
+        #                   f"m-185: left: {left}, top: {top}, width: {width}, height: {height}")
+
+        # --- 根据deepstream的左上点坐标,进行剪裁图像 ---
+        return image_array[int(top):int(top + height), int(left):int(left + width)]
+
+        # --- 根据deepstream的左上点坐标,计算出中心点,然后剪裁112尺寸的图像 --- todo 存在误识别问题
+        # half = 112/2
+        # return image_array[int(top + height/2 - half): int(top + height/2 + half),
+        #                    int(left + width/2 - half): int(left + width/2 + half)]
+
+    @staticmethod
+    def image_hex_to_image_array(hex_str_image, mode='RGB'):
+        """
+        数据格式转换
+        """
+        try:
+            bytes_image = binascii.unhexlify(hex_str_image)
+            _image = numpy_method.bytes_to_array_v2(bytes_image)
+            _image = cv2.imdecode(_image, cv2.IMREAD_COLOR)
+            return _image
+        except Exception as exception:
+            methods.debug_log('FaceDetectionEngine', f"m-138: exception | {exception}")
+            methods.debug_log('FaceDetectionEngine', f"m-138: traceback | {methods.trace_log()}")
+            return None
+
+
+if __name__ == '__main__':
+    # --- init ---
+    agent = FaceRecognitionEngine()
+    agent.prepare()
+
+    # --- test ---
+    # p0 = cv2.imread('./face.jpg')
+    # f0 = agent.get_face_features_by_image_array(p0)
+    # p3 = cv2.imread('./worker.jpg')
+    # p3 = cv2.resize(p3, (112, 112))
+    # f3 = agent.get_face_features_by_image_array(p3)
+    # sim = agent.compare_faces(f0, f3)
+    # print(f"Similarity: {sim}")
+
+    # --- test ---
+    import requests
+
+    url = 'https://lwres.yzw.cn/worker-avatar/Original/2020/1013/96c419ca-dbf2-4bf7-a072-92fde861a2bc.jpg'
+    response = requests.get(url, headers={'content-type': 'application/json'})
+    _image_bytes = response.content
+    f0 = agent.get_face_features_normalization_by_image_bytes(_image_bytes)
+    print(f"f0: {type(f0)}")
+
+    # p1 = cv2.imread('./face.jpg')
+    # f1 = agent.get_face_features_normalization_by_image_array(p1)
+    # sim = agent.compare_faces_by_normalization(f0, f1)
+    # print(f"Similarity: {sim}")

+ 10 - 0
3rdparty/xlib/__init__.py

@@ -0,0 +1,10 @@
+from xlib.xbase64 import *
+from xlib.xfile import *
+from xlib.xipv4 import *
+from xlib.xlist import *
+from xlib.xlog import *
+from xlib.xpickle import *
+from xlib.xsubprocess import *
+from xlib.xthread import *
+from xlib.xtime import *
+from xlib.xuuid import *

+ 51 - 0
3rdparty/xlib/xbase64.py

@@ -0,0 +1,51 @@
+# update: 2021-6-30-14
+import base64
+import hashlib
+
+
+def str_to_b64(string):
+    """b64加密"""
+    try:
+        return str(base64.b64encode(str(string).encode('utf-8')), 'utf-8')
+    except Exception as exception:
+        print(f"exception: {exception.__class__.__name__}")
+        return str()
+
+
+def b64_to_str(string):
+    """b64加密"""
+    try:
+        return str(base64.b64decode(str(string).encode('utf-8')), 'utf-8')
+    except Exception as exception:
+        print(f"exception: {exception.__class__.__name__}")
+        return str()
+
+
+def text_to_byte(text, mode='utf-8'):
+    return text.encode(mode)
+
+
+def byte_to_text(byte, mode='utf-8'):
+    return byte.decode(mode)
+
+
+def text_to_b64(text):
+    """b64编码"""
+    return str(base64.b64encode(str(text).encode('utf-8')), 'utf-8')
+
+
+def b64_to_text(text):
+    """b64解码"""
+    return str(base64.b64decode(str(text).encode('utf-8')), 'utf-8')
+
+
+def byte_to_b64(byte):
+    """b64加密"""
+    return base64.b64encode(byte)
+
+
+def byte_to_md5(byte):
+    """计算md5"""
+    m = hashlib.md5()
+    m.update(byte)
+    return m.hexdigest()

+ 248 - 0
3rdparty/xlib/xfile.py

@@ -0,0 +1,248 @@
+# update: 2024-3-31-10
+"""
+mode:
+    # r   只读,默认打开方式,当文件不存在时会报错
+    # w   只写,当文件不存在时会自动创建文件,文件内容只能是字符串,只能写入字符串
+    # r+  可读可写,当文件不存在时会报错
+    # w+  可读可写。当文件不存在时会新建
+    # a   追加文件,不可读
+    # a+  追加文件,可读可写
+    # rb  以二进制读模式打开,只可读
+    # rb+ 以二进制写读写模式打开,可读可写,当文件不存在时报错
+    # wb  以位进制写模式打开,只可写
+    # wb+ 以二进制读写模式打开,可读可写。当文件不存在时新建
+    # ab  以二进制追加模式打开,追加文件,不可读
+    # ab+ 以二进制读写模式打开,追加文件。可读可写
+"""
+import hashlib
+import base64
+import pickle
+import sys
+import os
+
+
+def read_bytes(file_path):
+    with open(file_path, 'rb') as f:
+        return f.read()
+
+
+def read_text(file_path):
+    with open(file_path, 'r') as f:
+        return f.read()
+
+
+def write_bytes(path, data=b''):
+    with open(path, 'wb') as f:
+        f.write(data)
+
+
+def write_text(path, data='', mode='w'):
+    with open(path, mode) as f:
+        f.write(data)
+
+
+def write_file(path, data=None, mode='wb'):
+    with open(path, mode) as f:
+        f.write(data)
+
+
+def load_pickle_file(file_path):
+    with open(file_path, 'rb+') as f:
+        return pickle.load(f)
+
+
+def save_pickle_file(file_path, data):
+    with open(file_path, 'wb') as f:
+        f.write(pickle.dumps(data))
+
+
+def get_file_md5(file_path):
+    """获取文件md5"""
+    m = hashlib.md5()
+    with open(file_path, 'rb') as f:
+        m.update(f.read())
+    return m.hexdigest()
+
+
+def get_big_file_md5(file_path, block_size=8 * 1024):
+    """获取文件md5(默认使用8KB作为分块大小)"""
+    m = hashlib.md5()
+    with open(file_path, 'rb') as f:
+        while True:
+            block = f.read(block_size)
+            if not block:
+                break
+            m.update(block)
+    return m.hexdigest()
+
+
+def _file_iterator(file_object, block_size=8 * 1024):
+    with file_object:
+        block = file_object.read(block_size)
+        while len(block) > 0:
+            yield block
+            block = file_object.read(block_size)
+
+
+def get_big_file_md5_v2(file_path):
+    """获取文件md5(默认使用8KB作为分块大小)"""
+    file_object = open(file_path, 'rb')
+    blocks = _file_iterator(file_object)
+    m = hashlib.md5()
+    for block in blocks:
+        m.update(block)
+    return m.hexdigest()
+
+
+def get_file_b64_md5(file_path):
+    """获取文件的b64的md5"""
+    m = hashlib.md5()
+    with open(file_path, 'rb') as f:
+        block = f.read()
+        encoded = base64.b64encode(block)
+        m.update(encoded)
+    return m.hexdigest()
+
+
+def get_big_file_b64_md5(file_path, block_size=3 * 1024 * 1024):
+    """流式获取文件的b64的md5(默认使用3MB作为分块大小)"""
+    with open(file_path, 'rb') as f1, open(f'{file_path}.b64', 'wb') as f2:
+        while True:
+            block = f1.read(block_size)
+            if not block:
+                break
+            b64_data = base64.b64encode(block)
+            f2.write(b64_data)
+    return get_big_file_md5(f'{file_path}.b64')
+
+
+def get_var_size(object, unit='MB'):
+    """
+    unit: GB/MB/KB/B
+    """
+    unit_dict = {
+        'GB': 3,
+        'MB': 2,
+        'KB': 1,
+        'B': 0,
+    }
+    byte = sys.getsizeof(object)
+    size = byte / (1024 ** unit_dict.get(unit))
+    return float(round(size, 1))
+
+
+def get_file_size(file_path, unit='MB'):
+    """
+    unit: GB/MB/KB/B
+    """
+    if not is_file(file_path):
+        return None
+    unit_dict = {
+        'GB': 3,
+        'MB': 2,
+        'KB': 1,
+        'B': 0,
+    }
+    byte = os.path.getsize(file_path)
+    size = byte / (1024 ** unit_dict.get(unit))
+    return float(round(size, 1))
+
+
+def get_file_path_list(dir_path='/root', path_list=None):
+    """
+    获取指定目录下全部文件
+    Example:
+        get_file_path_list('/opt')
+        ['/opt/1.txt', '/opt/2.txt']
+
+    # import glob
+    # if os.path.isfile(input_dir):
+    #     img_list = [input_dir]
+    # else:
+    #     img_list = sorted(glob.glob(os.path.join(input_dir, '*')))
+    """
+    if not path_list:
+        path_list = []
+    for path, folders, files in os.walk(dir_path):
+
+        for file_name in files:
+            file_path = os.path.join(path, file_name)
+            if file_path in path_list:
+                continue
+            # if file_path.split('.')[-1] == 'pdb':
+            #     remove_file(file_path)
+            #     print(f"#file_path: {file_path}")
+            path_list.append(file_path)
+
+        for folder_name in folders:
+            get_file_path_list(os.path.join(path, folder_name), path_list)
+
+    return path_list
+
+
+def mkdir(dir_path):
+    """
+    创建文件目录
+    Example:
+        mkdir('/share/abc/xyz/1.txt')
+        True
+    """
+    if not dir_path:
+        return False
+
+    if dir_path[0] != '/':
+        dir_path = f"{get_pwd()}/{dir_path}"
+
+    path = '/'
+    for part in dir_path.split('/'):
+        path += f"{part}/"
+        if not os.path.isdir(path):
+            os.mkdir(path)
+    return True
+
+
+def get_pwd():
+    """
+    获取当前路径
+    """
+    path = sys.path[0]
+    if not os.path.isdir(path):
+        path = os.path.dirname(path)
+    return path
+
+
+def is_file(file_path):
+    """判断文件"""
+    return os.path.isfile(file_path)
+
+
+def is_dir(file_path):
+    """判断目录"""
+    return os.path.isdir(file_path)
+
+
+def remove_file(file_path):
+    """删除文件"""
+    return os.remove(file_path)
+
+
+def move_file(s_path, d_path):
+    """移动文件"""
+    return os.rename(s_path, d_path)
+
+
+if __name__ == '__main__':
+    """
+    """
+    # --- test ---
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoQt'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoQt\thirdparty'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoServer\webrtcinterop\x64\Release'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoQt\webrtcinterop\x64\Release'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoServer'
+    file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoQt'
+    # file_dir = r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoServer'
+    path_list = get_file_path_list(file_dir)
+    print(path_list)
+    # remove_file(r'E:\casper\repositories\repositories\SRI-DYZBC.Cockpit-cpp\EgoQt\webrtcinterop\x64\Release\webrtcinterop.obj')

+ 26 - 0
3rdparty/xlib/xipv4.py

@@ -0,0 +1,26 @@
+# update: 2021-6-21-16
+
+
+def int_to_ip(integer):
+    """
+    整型转ip
+    """
+    raw = bin(int(integer)).lstrip('0b').zfill(32)
+    return '%d.%d.%d.%d' % (int(raw[0:8], 2), int(raw[8:16], 2), int(raw[16:24], 2), int(raw[24:32], 2))
+
+
+def ip_to_int(ip):
+    """
+    ip转整型
+    """
+    if not ip:
+        return 0
+    ip = str(ip)
+    raw = ip.split('.')
+    if len(raw) != 4:
+        return 0
+    binnum = bin(int(raw[0])).lstrip('0b').zfill(8)
+    binnum += bin(int(raw[1])).lstrip('0b').zfill(8)
+    binnum += bin(int(raw[2])).lstrip('0b').zfill(8)
+    binnum += bin(int(raw[3])).lstrip('0b').zfill(8)
+    return int(binnum, 2)

+ 28 - 0
3rdparty/xlib/xlist.py

@@ -0,0 +1,28 @@
+# update: 2022-6-17
+
+
+def get_last(data_list, amount):
+    """获取尾部元素"""
+    return data_list[-amount:]
+
+
+def cut_last(data_list, amount):
+    """去掉尾部元素"""
+    return data_list[:-amount]
+
+
+def get_head(data_list, amount):
+    """获取头部元素"""
+    return data_list[:amount]
+
+
+def cut_head(data_list, amount):
+    """去掉头部元素"""
+    return data_list[amount:]
+
+
+def reverse(string_or_list):
+    """倒序"""
+    if type(string_or_list) not in [str, list]:
+        return ''
+    return string_or_list[::-1]

+ 26 - 0
3rdparty/xlib/xlog.py

@@ -0,0 +1,26 @@
+# update: 2022-4-19
+import logging
+import traceback
+
+LOG = logging.getLogger(__name__)
+# logging.basicConfig(format='%(levelname)s | %(asctime)s | %(module)s.%(funcName)s:%(lineno)s >>> %(message)s',
+#                     level=logging.DEBUG)
+logging.basicConfig(format=f"INFO:     %(message)s", level=logging.INFO)
+
+
+def debug_log(tags, args, is_work=True, show_level=logging.INFO):
+    """"""
+    if not is_work:
+        return
+    elif type(args) == list:
+        lines = f"{tags}:"
+        for count, line in enumerate(args):
+            lines += f"\n- {count + 1} - {line}"
+        LOG.info(lines)
+    elif type(args) == str:
+        LOG.info(f"{tags} | {args}")
+
+
+def trace_log():
+    """"""
+    return traceback.format_exc()

+ 54 - 0
3rdparty/xlib/xmarkdwon.py

@@ -0,0 +1,54 @@
+# update: 2024.9.4
+"""
+cd /media/nvidia/nvme0n1/server/repositories/repositories/sri-project.demo-py/3rdparty/xlib
+python3 xmarkdwon.py
+"""
+
+
+def read_text(file_path):
+    with open(file_path, 'r') as f:
+        return f.read()
+
+
+if __name__ == '__main__':
+    file_path = '/media/nvidia/nvme0n1/server/repositories/repositories/sri-project.demo-py/sri-agent-tool01/factories/README-test.md'
+
+    # --- 获取唯一的表格数据,
+    text = read_text(file_path)
+    t_list = text.split('|')[1:-1]
+    table_text = '|' + '|'.join(t_list) + '|'
+    print(table_text)
+    # | 名称     | 年龄 | 职业     |
+    # |----------|------|----------|
+    # | 张三     | 28   | 软件工程师 |
+    # | 李四     | 35   | 数据科学家 |
+    # | 王五     | 22   | 产品经理   |
+
+    # --- 字符转字典
+    # 分割表格行
+    lines = table_text.strip().split("\n")
+    # 获取表头
+    headers = [header.strip() for header in lines[0].split("|") if header]
+    # 解析数据行
+    data = []
+    for line in lines[2:]:  # 跳过表头分隔线
+        values = [value.strip() for value in line.split("|") if value]
+        row = dict(zip(headers, values))
+        data.append(row)
+    print(data)
+
+    # --- 字典转字符
+    # 获取表头
+    headers = data[0].keys()
+    # 创建表头行
+    header_row = "| " + " | ".join(headers) + " |"
+    # 创建分隔行
+    separator_row = "| " + " | ".join(["-" * len(header) for header in headers]) + " |"
+    # 创建数据行
+    data_rows = []
+    for item in data:
+        row = "| " + " | ".join(item.values()) + " |"
+        data_rows.append(row)
+    # 合并所有行
+    markdown_table = "\n".join([header_row, separator_row] + data_rows)
+    print(markdown_table)

+ 93 - 0
3rdparty/xlib/xpickle.py

@@ -0,0 +1,93 @@
+# update: 2021-6-30-11
+import pickle
+import json
+
+
+def pickle_dumps(obj):
+    """压缩数据"""
+    return pickle.dumps(obj)
+
+
+def pickle_loads(obj):
+    """解压数据"""
+    return pickle.loads(obj)
+
+
+def json_dumps(obj):
+    """压缩数据"""
+    return json.dumps(obj)
+
+
+def json_loads(obj):
+    """解压数据"""
+    return json.loads(obj)
+
+
+def is_json(string):
+    """是否json"""
+    try:
+        if string:
+            json.loads(string)
+        else:
+            return False
+    except Exception as exception:
+        return False
+    return True
+
+
+def str_to_re(string):
+    """
+    转义正则特殊符号
+    """
+    string = string.replace('$', r'\$')
+    string = string.replace('(', r'\(')
+    string = string.replace(')', r'\)')
+    string = string.replace('*', r'\*')
+    string = string.replace('.', r'\.')
+    string = string.replace('[', r'\[')
+    string = string.replace(']', r'\]')
+    string = string.replace('?', r'\?')
+    string = string.replace('\\', '\\\\')
+    string = string.replace('^', r'\^')
+    string = string.replace('{', r'\{')
+    string = string.replace('}', r'\}')
+    string = string.replace('|', r'\|')
+    return string
+
+
+def str_to_json(string):
+    """
+    eval转换
+    """
+    null = None
+    true = True
+    false = False
+    return eval(string)
+
+
+def json_to_str(input_data):
+    """
+    eval转换
+    """
+    if type(input_data) == dict:
+        for k, v in input_data.items():
+            if type(v) != str:
+                continue
+            input_data[k] = v.replace('\'', '')
+    data = str(input_data).replace('\'', '\"')
+    data = str(data).replace('None', 'null')
+    data = str(data).replace('True', 'true')
+    data = str(data).replace('False', 'false')
+    return data
+
+
+if __name__ == '__main__':
+    # --- test ---
+    # a = {111: 222}
+    # print(json_dumps(a))
+    # print(json_loads(json_dumps(a)))
+
+    # --- test ---
+    a = b'\x80\x03N.'
+    o = pickle_loads(a)
+    print(o, type(o))

+ 27 - 0
3rdparty/xlib/xsubprocess.py

@@ -0,0 +1,27 @@
+# update: 2024.9.4
+"""
+cd /media/nvidia/nvme0n1/server/repositories/repositories/sri-project.demo-py/3rdparty/xlib
+python3 xsubprocess.py
+"""
+import subprocess
+
+
+def run_command(command, callback=False):
+    """
+    执行shell命令
+    """
+    if callback:
+        obj = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+        lines = obj.stdout.readlines()
+        if len(lines) == 1:
+            lines = str(lines[0], encoding='utf-8').strip('\n')
+        return lines
+    else:
+        return subprocess.Popen(command, shell=True)
+
+
+if __name__ == '__main__':
+    command_line = f'find /media/nvidia/nvme0n1/ZJ_PRO_test -type f | wc -l'
+    # command_line = 'du -sh /media/nvidia/nvme0n1/test | awk \'{print $1}\''
+    out = run_command(command_line, callback=True)
+    print(out)

+ 44 - 0
3rdparty/xlib/xthread.py

@@ -0,0 +1,44 @@
+from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
+import time
+
+
+class Executor(object):
+
+    def __init__(self, callback=False):
+        self.pool = ThreadPoolExecutor(max_workers=300)
+        # self.pool = ProcessPoolExecutor(4)
+        self.tasks = []
+        self.callback = callback
+
+    def run(self, method, *args):
+        """增加任务"""
+        task = self.pool.submit(method, *args)
+        if self.callback:
+            self.tasks.append(task)
+        while self.callback:
+            results = []
+            for task in self.tasks:
+                results.append(task.done())
+            if len(set(results)) == 1 and results[0] == True:
+                break
+
+    def close(self):
+        """关闭线程池"""
+        self.pool.shutdown()
+
+
+def f1():
+    print(time.time() - s)
+    a = sum([i for i in range(9999999)])
+
+
+if __name__ == '__main__':
+
+    s = time.time()
+    # --- 5.139862537384033 ---
+    e = Executor()
+    for i in range(5):
+        e.run(f1)
+    e.close()
+
+    print(time.time() - s)

+ 169 - 0
3rdparty/xlib/xtime.py

@@ -0,0 +1,169 @@
+# update: 2021-10-21
+"""
+%a 星期的简写。如 星期三为Web
+%A 星期的全写。如 星期三为Wednesday
+%b 月份的简写。如4月份为Apr
+%B月份的全写。如4月份为April
+%c: 日期时间的字符串表示。(如: 04/07/10 10:43:39)
+%d: 日在这个月中的天数(是这个月的第几天)
+%f: 微秒(范围[0,999999])
+%H: 小时(24小时制,[0, 23])
+%I: 小时(12小时制,[0, 11])
+%j: 日在年中的天数 [001,366](是当年的第几天)
+%m: 月份([01,12])
+%M: 分钟([00,59])
+%p: AM或者PM
+%S: 秒(范围为[00,61],为什么不是[00, 59],参考python手册~_~)
+%U: 周在当年的周数当年的第几周),星期天作为周的第一天
+%w: 今天在这周的天数,范围为[0, 6],6表示星期天
+%W: 周在当年的周数(是当年的第几周),星期一作为周的第一天
+%x: 日期字符串(如:04/07/10)
+%X: 时间字符串(如:10:43:39)
+%y: 2个数字表示的年份
+%Y: 4个数字表示的年份
+%z: 与utc时间的间隔 (如果是本地时间,返回空字符串)
+%Z: 时区名称(如果是本地时间,返回空字符串)
+"""
+import datetime
+import time
+
+UTC_PATTERN = '%Y-%m-%dT%H:%M:%S.%fZ'
+LOCAL_PATTERN = '%Y-%m-%d %H:%M:%S'
+
+
+def string_to_dt(string, pattern='%Y-%m-%d'):
+    """
+    字符串转为日期
+    """
+    return datetime.datetime.strptime(string, pattern)
+
+
+def dt_to_string(date, pattern='%Y-%m-%d %H:%M:%S'):
+    """
+    日期转为字符串
+    """
+    return date.strftime(pattern)
+
+
+def string_to_ts(string, pattern='%Y-%m-%dT%H:%M:%S.%f'):
+    """
+    日期转为时间戳
+    Example:
+        string_to_ts(2015-01-01)
+        1420041600
+    """
+    return int(time.mktime(time.strptime(string, pattern)))
+
+
+def ts_to_string(int_time, pattern='%Y-%m-%dT%H:%M:%S.%f'):
+    """
+    时间戳->字符串
+    """
+    return time.strftime(pattern, time.localtime(int_time))
+
+
+def now_string(pattern='%Y-%m-%d %H:%M:%S'):
+    """
+    获取当前时间戳
+    """
+    return datetime.datetime.now().strftime(pattern)
+
+
+def iso_string_to_ts(string):
+    """
+    iso标准时间转时间戳
+    """
+    return time.mktime(time.strptime(string, '%Y-%m-%dT%H:%M:%S.%f'))
+
+
+def now_iso_string():
+    """
+    iso标准时间
+    """
+    return datetime.datetime.now().isoformat('T')
+
+
+def now_utc_iso_string():
+    """
+    iso标准时间
+    """
+    return datetime.datetime.utcnow().isoformat('T')
+
+
+def now_utc_dt():
+    """
+    获取当前utc时间
+    """
+    return datetime.datetime.utcnow()
+
+
+def now_dt():
+    """
+    获取当前时间
+    Example:
+        now_dt()
+        datetime.datetime(2020, 5, 16, 6, 15, 7, 39060)
+    """
+    return datetime.datetime.now()
+
+
+def now_ts(unit='s', ndigits=0):
+    """
+    获取当前时间戳
+    unit: 单位 (s: 秒 ms: 毫秒)
+    ndigits: 小数点位数
+    """
+    unit_dict = {
+        'ms': 1,
+        's': 0,
+    }
+    ts = time.time()
+    if unit_dict.get(unit):
+        ts = ts * (1000 ** unit_dict.get(unit))
+    if ndigits:
+        return round(ts, ndigits)
+    return int(ts)
+
+
+def today_ts():
+    """
+    获取当日时间戳
+    """
+    today_dt = datetime.date.today()
+    return int(time.mktime(today_dt.timetuple()))
+
+
+def utc_to_beijing(utc_string, pattern='%Y-%m-%dT%H:%M:%S.%fZ'):
+    """UTC时间转北京时间(+8:00)"""
+    utc_ts = string_to_ts(utc_string, pattern=pattern)
+    beijing_ts = utc_ts + (3600 * 8)
+    return ts_to_string(beijing_ts, pattern='%Y-%m-%d %H:%M:%S')
+
+
+def beijing_to_utc(beijing_string, pattern='%Y-%m-%d %H:%M:%S'):
+    """本地时间转UTC时间(-8:00)"""
+    beijing_ts = string_to_ts(beijing_string, pattern=pattern)
+    utc_ts = beijing_ts - (3600 * 8)
+    return ts_to_string(utc_ts, pattern='%Y-%m-%dT%H:%M:%S.%fZ')
+
+
+if __name__ == '__main__':
+    # t = '2020-08-02T01:00:00.000Z'
+    # print(utc_to_beijing(t))
+
+    # t = "2020-08-02T16:00:00.000Z"
+    # d = string_to_dt(t, "%Y-%m-%dT%H:%M:%S.%fZ")
+    # print(d)
+
+    # print(datetime.datetime.now() + datetime.timedelta(minutes=-1)).strftime("%Y-%m-%d %H:%M:%S")
+    # print(today_ts())
+    # print(string_to_ts('2021-03-25-09-53-39', '%Y-%m-%d-%H-%M-%S'))
+
+    # print(now_ts(unit='ms'))
+    # print(now_ts(unit='s'))
+    print(now_ts())
+
+    # out = string_to_ts('2021-07-16', '%Y-%m-%d')
+    # print(out)
+
+    # print(string_to_ts('2022-06-01 22:00:00', pattern='%Y-%m-%d %H:%M:%S'))  # 1654092000

+ 16 - 0
3rdparty/xlib/xuuid.py

@@ -0,0 +1,16 @@
+# update: 2021-6-28-19
+import uuid
+
+
+def is_uuid4(string):
+    """验证uuid4"""
+    try:
+        uuid.UUID(string, version=4)
+        return True
+    except ValueError:
+        return False
+
+
+def new_uuid4():
+    """生成uuid4"""
+    return str(uuid.uuid4())

+ 406 - 0
3rdparty/xpip/aes_by_crypto.py

@@ -0,0 +1,406 @@
+# update: 2021-5-12-21
+from Crypto.Cipher import AES
+import base64
+import hashlib
+import zipfile
+import pyminizip
+import pyzipper
+import os
+
+
+class _AES(object):
+    def __init__(self, key='7486E0264E999881D4EF7BEEDF05A9F7', model='ECB', iv='',
+                 code_type='utf-8'):
+        """
+        code_type: utf-8/gbk
+        """
+        self.code_type = code_type
+        self.model = {'ECB': AES.MODE_ECB, 'CBC': AES.MODE_CBC}[model]
+        self.key = self.replenish(key)
+
+        # --- create aes object ---
+        if model == 'ECB':
+            self.aes = AES.new(self.key, self.model)
+        elif model == 'CBC':
+            self.aes = AES.new(self.key, self.model, iv)
+
+    def replenish(self, block, block_size=16):
+        """block_size: AES key must be either 16, 24, or 32 bytes long"""
+        block = block.encode(self.code_type)
+        while len(block) % block_size != 0:
+            block += b'\x00'
+        return block
+
+    def encrypt(self, text):
+        text = self.replenish(text)
+        encrypt_text = self.aes.encrypt(text)
+        return base64.encodebytes(encrypt_text).decode().strip()
+
+    def decrypt(self, text):
+        text = base64.decodebytes(text.encode(self.code_type))
+        decrypt_text = self.aes.decrypt(text)
+        return decrypt_text.decode(self.code_type).strip('\0')
+
+
+def text_to_b64(string):
+    """b64编码"""
+    return str(base64.b64encode(str(string).encode('utf-8')), 'utf-8')
+
+
+def b64_to_text(string):
+    """b64解码"""
+    return str(base64.b64decode(str(string).encode('utf-8')), 'utf-8')
+
+
+def text_to_aes(text, key='YourPassword'):
+    """aes加密"""
+    aes = _AES(key=key)
+    return text_to_b64(aes.encrypt(text))
+
+
+def aes_to_text(text, key='YourPassword'):
+    """aes解密"""
+    aes = _AES(key=key)
+    return aes.decrypt(b64_to_text(text))
+
+
+def get_big_file_md5(file_path, block_size=8 * 1024):
+    """获取文件md5(默认使用8KB作为分块大小)"""
+    m = hashlib.md5()
+    with open(file_path, 'rb') as f:
+        while True:
+            block = f.read(block_size)
+            if not block:
+                break
+            m.update(block)
+    return m.hexdigest()
+
+
+def file_to_zip(file_path):
+    """生成zip压缩文件"""
+
+    # --- encrypt file name --- todo 如果aes后超过255的长度就不做aes了
+    file_name = file_path.split('/')[-1]
+    save_name = text_to_b64(file_name)
+
+    # --- define zip name ---
+    file_md5 = get_big_file_md5(file_path)
+    zip_name = f"{file_md5}.zip"
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    zip_path = f"{dir_path}/{zip_name}"
+
+    # --- writing ---
+    with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as f1:
+        f1.write(file_path, arcname=save_name)
+
+
+def zip_to_file():
+    pass
+
+
+def file_to_zip_v2(file_path, key='<YourPassword@2021>', compress_level=5):
+    """
+    文件转zip
+    compress_level(int) between 1 to 9, 1 (more fast) <---> 9 (more compress) or 0 (default)
+    """
+    # --- check parameter ---
+    if not os.path.isfile(file_path):
+        return dict(code=1, details='parameter error!')
+
+    # --- encrypt file name ---
+    file_name = file_path.split('/')[-1]
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    save_name = text_to_aes(file_name, key=key)
+
+    # --- check again ---
+    is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+    if is_encrypted:
+        return dict(code=2, details='parameter error!')
+
+    # --- check system support ---
+    if len(save_name) > 250:
+        return dict(code=3, details='parameter error!')
+
+    # --- rename file ---
+    save_path = f"{dir_path}/{save_name}"
+    os.rename(file_path, save_path)
+
+    # --- define zip name ---
+    file_md5 = get_big_file_md5(save_path)
+    zip_path = f"{dir_path}/{file_md5}.{save_name[:4]}"
+
+    try:
+        # --- writing ---
+        """
+        Args:
+            1. src file path (string)
+            2. src file prefix path (string) or None (path to prepend to file)
+            3. dst file path (string)
+            4. password (string) or None (to create no-password zip)
+            5. compress_level(int) between 1 to 9, 1 (more fast) <---> 9 (more compress) or 0 (default)
+        """
+        pyminizip.compress(save_path, None, zip_path, key, compress_level)
+
+        # --- remove file ---
+        os.remove(save_path)
+        return dict(code=0, details='ended.')
+
+    except Exception as exception:
+
+        # --- revert ---
+        os.rename(save_path, file_path)
+
+        import traceback
+        print(traceback.format_exc())
+        return dict(code=-1, details=f"{traceback.format_exc()}")
+
+
+def zip_to_file_v2(file_path, key='<YourPassword@2021>', no_path=True):
+    """zip转file"""
+
+    # --- check parameter ---
+    if not os.path.isfile(file_path):
+        return dict(code=1, details='parameter error!')
+
+    # --- check again ---
+    file_name = file_path.split('/')[-1]
+    is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+    if not is_encrypted:
+        return dict(code=2, details='parameter error!')
+
+    # --- record ---
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    names = os.listdir(dir_path)
+    before_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+    try:
+        # --- writing ---
+        """
+        Args:
+            1. src file path (string)
+            2. password (string) or None (to unzip encrypted archives)
+            3. dir path to extract files or None (to extract in a specific dir or cwd)
+            4. withoutpath (exclude path of extracted)
+        """
+        pyminizip.uncompress(file_path, key, dir_path, no_path)
+
+        # --- record ---
+        names = os.listdir(dir_path)
+        after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+        # --- rename ---
+        for path in list(after_files - before_files):
+            name = path.split('/')[-1]
+            raw_name = aes_to_text(name, key=key)
+            os.rename(path, f"{dir_path}/{raw_name}")
+
+        # --- remove file ---
+        os.remove(file_path)
+        return dict(code=0, details='ended.')
+
+    except Exception as exception:
+
+        # --- revert ---
+        names = os.listdir(dir_path)
+        after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+        for path in list(after_files - before_files):
+            os.remove(path)
+
+        import traceback
+        print(traceback.format_exc())
+        return dict(code=-1, details=f"{traceback.format_exc()}")
+
+
+def file_to_zip_v2_1(file_path, key='<YourPassword@2021>', compress_level=1):
+    """file转zip(双文件保存版,其中一个文件保存加密文件名)"""
+
+    # --- check parameter ---
+    if not os.path.isfile(file_path):
+        return dict(code=1, details='parameter error!')
+
+    # --- check again ---
+    file_name = file_path.split('/')[-1]
+    is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+    if is_encrypted:
+        return dict(code=2, details='parameter error!')
+
+    # --- create name file ---
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    file_name_aes = text_to_aes(file_name, key=key)
+    name_file_path = f"{dir_path}/{file_name_aes[:4]}"
+    with open(name_file_path, 'w') as f1:
+        f1.write(file_name_aes)
+
+    # --- rename file ---
+    file_md5 = get_big_file_md5(file_path)
+    data_file_path = f"{dir_path}/{file_md5}"
+    os.rename(file_path, data_file_path)
+
+    # --- define zip name ---
+    zip_path = f"{dir_path}/{file_md5}.{file_name_aes[:4]}"
+
+    try:
+        # --- writing ---
+        # pyminizip.compress(save_path, None, zip_path, key, compress_level)
+        """
+        Args:
+            1. src file LIST path (list)
+            2. src file LIST prefix path (list) or []
+            3. dst file path (string)
+            4. password (string) or None (to create no-password zip)
+            5. compress_level(int) between 1 to 9, 1 (more fast) <---> 9 (more compress)
+            6. optional function to be called during processing which takes one argument, the count of how many files have been compressed
+        """
+        pyminizip.compress_multiple([data_file_path, name_file_path], ['', ''], zip_path, key, compress_level)
+
+        # --- remove file ---
+        os.remove(data_file_path)
+        os.remove(name_file_path)
+        return dict(code=0, details='ended.')
+
+    except Exception as exception:
+
+        # --- revert ---
+        os.remove(name_file_path)
+        os.rename(data_file_path, file_path)
+
+        import traceback
+        print(traceback.format_exc())
+        return dict(code=-1, details=f"{traceback.format_exc()}")
+
+
+def zip_to_file_v2_1(file_path, key='<YourPassword>', no_path=True):
+    """zip转file(双文件保存版,其中一个文件保存加密文件名)"""
+
+    # --- check parameter ---
+    if not os.path.isfile(file_path):
+        return dict(code=1, details='parameter error!')
+
+    # --- check again ---
+    file_name = file_path.split('/')[-1]
+    is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+    if not is_encrypted:
+        return dict(code=2, details='parameter error!')
+
+    # --- record ---
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    names = os.listdir(dir_path)
+    before_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+    try:
+        # --- writing ---
+        """
+        Args:
+            1. src file path (string)
+            2. password (string) or None (to unzip encrypted archives)
+            3. dir path to extract files or None (to extract in a specific dir or cwd)
+            4. withoutpath (exclude path of extracted)
+        """
+        pyminizip.uncompress(file_path, key, dir_path, no_path)
+
+        # --- record ---
+        names = os.listdir(dir_path)
+        after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+        # --- check ---
+        temp_file = list(after_files - before_files)
+        if len(temp_file) != 2:
+            raise Exception('something is wrong!')
+
+        # --- get file name ---
+        raw_name = str()
+        for path in temp_file:
+            name = path.split('/')[-1]
+            if len(name) == 4:
+                with open(path, 'r') as f1:
+                    raw_name = aes_to_text(f1.read(), key=key)
+                os.remove(path)
+
+        # --- rename ---
+        for path in list(after_files - before_files):
+            name = path.split('/')[-1]
+            if len(name) == 32:
+                os.rename(path, f"{dir_path}/{raw_name}")
+
+        # --- remove file ---
+        os.remove(file_path)
+        return dict(code=0, details='ended.')
+
+    except Exception as exception:
+
+        # --- revert ---
+        names = os.listdir(dir_path)
+        after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+        for path in list(after_files - before_files):
+            os.remove(path)
+
+        import traceback
+        print(traceback.format_exc())
+        return dict(code=-1, details=f"{traceback.format_exc()}")
+
+
+def file_to_zip_v3(file_path, key='YourPassword', aes_bits=128):
+    """
+    生成aes加密zip压缩文件
+    aes_bits: 128/192/256
+    """
+    # --- check parameter ---
+    if not os.path.isfile(file_path):
+        return dict(code=1, details='parameter error!')
+
+    # --- encrypt file name ---
+    file_name = file_path.split('/')[-1]
+    save_name = text_to_aes(file_name, key=key)
+
+    # --- check system support ---
+    if len(save_name) > 250:
+        return dict(code=2, details='parameter error!')
+
+    # --- define zip name ---
+    file_md5 = get_big_file_md5(file_path)
+    zip_name = f"{file_md5}.zip"
+    dir_path = '/'.join(file_path.split('/')[:-1])
+    zip_path = f"{dir_path}/{zip_name}"
+
+    # --- writing ---
+    with pyzipper.AESZipFile(zip_path, 'w', compression=pyzipper.ZIP_LZMA) as f1:
+        f1.setpassword(key.encode())
+        f1.setencryption(pyzipper.WZ_AES, nbits=aes_bits)
+        f1.write(file_path, arcname=save_name)
+    return dict(code=0, details='ended.')
+
+
+def zip_to_file_v3(file_path, key='YourPassword'):
+    """生成aes加密zip压缩文件"""
+
+    dir_path = '/'.join(file_path.split('/')[:-1])
+
+    # --- reading ---
+    with pyzipper.AESZipFile(file_path) as f1:
+        f1.setpassword(key.encode())
+        file_list = f1.namelist()
+        for file_name in file_list:
+            # --- decrypt file name ---
+            raw_name = aes_to_text(file_name, key=key)
+
+            # --- writing ---
+            with open(f"{dir_path}/{raw_name}", 'wb') as f2:
+                f2.write(f1.read(file_name))
+    return dict(code=0, details='ended.')
+
+
+def file_to_7z(file_path, key='YourPassword'):
+    pass
+
+
+def folder_to_zip(folder_path):
+    pass
+
+
+if __name__ == '__main__':
+    # text1 = '0/588564d4-3bca-4ebf-83d8-1155963fbfa9/3066753951/1593578486/1595174400'
+    text2 = 'eFVqYzZjZ3hneVZ0T09uVVlHTm1rUT09'
+    # text2 = text_to_aes(text1, '123456')
+    test3 = aes_to_text(text2, '123456')
+    # print('密文:', text2)
+    print('明文:', test3)

+ 65 - 0
3rdparty/xpip/camera_by_cv2.py

@@ -0,0 +1,65 @@
+# update: 2022-2-23
+"""
+image.shape: 图片尺寸 (height, width, number)
+"""
+import cv2 as cv
+import time
+
+
+def get_capture_once(capture_path):
+    """获取capture"""
+
+    # --- check ---
+    cap = cv.VideoCapture(capture_path)
+    if not cap.isOpened():
+        return None
+
+    # --- check ---
+    fps = int(cap.get(cv.CAP_PROP_FPS))
+    if not 4 < fps < 100:
+        return None
+
+    return cap
+
+
+def get_capture(capture_path):
+    """获取capture"""
+
+    while True:
+
+        # --- check ---
+        cap = cv.VideoCapture(capture_path)
+        if not cap.isOpened():
+            print('m1: sleep 3s.')
+            time.sleep(3)
+            continue
+
+        # --- check ---
+        fps = int(cap.get(cv.CAP_PROP_FPS))
+        if not 4 < fps < 100:
+            print('m2: sleep 3s.')
+            time.sleep(3)
+            continue
+
+        return cap
+
+
+def rtsp_is_live(capture_path):
+    """"""
+
+    # --- check ---
+    cap = cv.VideoCapture(capture_path)
+    if not cap.isOpened():
+        return False
+
+    # --- check ---
+    fps = int(cap.get(cv.CAP_PROP_FPS))
+    if not 4 < fps < 100:
+        return False
+
+    return True
+
+
+if __name__ == '__main__':
+    print(get_capture('rtsp://admin:DEVdev123@192.168.30.235:554/h264/ch1/sub/av_stream'))
+    print(rtsp_is_live('rtsp://admin:DEVdev123@192.168.30.235:554/h264/ch1/sub/av_stream'))

+ 40 - 0
3rdparty/xpip/data_by_numpy.py

@@ -0,0 +1,40 @@
+# update: 2022-6-1-10
+import numpy as np
+
+
+def to_array(data):
+    return np.array(data)
+
+
+def to_bytes(array):
+    return array.tobytes()
+
+
+def bytes_to_array(_bytes):
+    return np.frombuffer(_bytes, dtype=np.uint8)
+
+
+def bytes_to_array_v2(_bytes):
+    return np.asarray(bytearray(_bytes), dtype='uint8')
+
+
+def string_to_array(string):
+    return np.fromstring(string, dtype=np.uint8)
+
+
+if __name__ == '__main__':
+    import cv2
+    import pickle
+
+    cap = cv2.VideoCapture('rtsp://admin:DEVdev123@192.168.30.235:554/h264/ch1/sub/av_stream')
+    ret, frame = cap.read()
+    print(frame.shape)
+    print(type(frame))
+
+    _bytes = pickle.dumps(frame)
+    print(type(_bytes))
+
+    _array = pickle.loads(_bytes)
+    print(type(_array))
+
+    print(np.array_equal(frame, _array))

+ 30 - 0
3rdparty/xpip/show_by_prettytable.py

@@ -0,0 +1,30 @@
+# update: 2021-1-15-18
+import prettytable as pt
+import logging
+
+logger = logging.getLogger('log_method')
+
+
+def show_logs(tags, args, is_work=True):
+	"""log格式化"""
+	if not is_work:
+		return
+	if type(args) == dict:
+		text = pt.PrettyTable()
+		text.field_names = ['参数', '类型', '值']
+		text.align = 'l'
+		for name, value in args.items():
+			too_long = len(str(value)) > 200
+			if too_long:
+				dots = '..'
+			else:
+				dots = ''
+			text.add_row([name, str(type(value)), str(repr(value))[:200] + dots])
+		logger.info(f"{tags}:\n{text}")
+	elif type(args) == list:
+		lines = f"{tags}:"
+		for count, line in enumerate(args):
+			lines += f"\n- {count + 1} - {line}"
+		logger.info(lines)
+	else:
+		logger.info(f"{tags}: {args}")

+ 252 - 0
3rdparty/xpip/xapscheduler.py

@@ -0,0 +1,252 @@
+# date: 2021-10-13
+"""
+# 每天0点:add_job(trigger='cron', hour='0')
+# 每1小时:add_job(trigger='interval', hour='1')
+# 每1分钟:add_job(trigger='cron', minutes='*/1')
+tips: 如果希望加强测试或重度使用,则推荐循环操作;如果轻度使用或保守推进,建议手动指定下次操作时间。
+"""
+from apscheduler.schedulers.background import BackgroundScheduler
+from apscheduler.jobstores.mongodb import MongoDBJobStore
+from apscheduler.executors.pool import ThreadPoolExecutor
+from pymongo import MongoClient
+import uuid
+
+
+class APS(object):
+
+    def __init__(self, db_type='mongo', db_host='aibox-middleware-mongo', db_port=27017,
+                 username='admin', password='admin', database='aibox', collection='LoopTask', is_clean=True):
+        """
+        内部访问:
+            host: sri-thirdparty-mongo
+            port: 27017
+        远程访问:
+            host: 118.190.217.96、192.168.20.162
+            port: 7030
+        """
+        # todo 默认使用sqlite数据库,而不是mongo
+        mongo = MongoClient(db_host, db_port, username=username, password=password)
+        if is_clean:
+            mongo[database][collection].delete_many({})
+
+        jobstores = {
+            'default': MongoDBJobStore(client=mongo, database=database, collection=collection),
+        }
+        executors = {
+            'default': ThreadPoolExecutor(max_workers=3)
+        }
+        self.scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, timezone='Asia/Shanghai')
+        self.scheduler.start()  # todo 不支持client方式查看job列表
+
+    def get_all(self):
+        """
+        获取任务列表
+        """
+        job_list = []
+        jobs = self.scheduler.get_jobs()
+        for job in jobs:
+            data = {
+                'id': job.id,
+                'name': job.name,
+                'func': job.func.__name__,
+                'args': list(job.args),
+                'trigger': str(job.trigger),
+                'next_run_time': job.next_run_time.strftime('%Y-%m-%d %H:%M:%S'),
+            }
+            job_list.append(data)
+        return job_list
+
+    def get_one(self, job_uuid):
+        """获取任务详情"""
+        job = self.scheduler.get_job(job_uuid)
+        data = {
+            'id': job.id,
+            'name': job.name,
+            'func': job.func.__name__,
+            'args': list(job.args),
+            'trigger': str(job.trigger),
+            'next_run_time': job.next_run_time.strftime('%Y-%m-%d %H:%M:%S'),
+        }
+        return data
+
+    def create_job(self, **params):
+        """
+        创建任务
+        trigger: 触发器
+            date 固定时间调度(只会执行一次)
+            interval 固定时间间隔
+            cron 定时调度
+        """
+        params['id'] = str(uuid.uuid4())
+        # return self.scheduler.add_job(**params)
+        return self.scheduler.add_job(**params, max_instances=100)  # fix maximum number of running instances reached
+
+    def remove_all(self):
+        """删除全部任务"""
+        jobs = self.scheduler.get_jobs()
+        job_ids = [i.id for i in jobs]
+        for job_id in job_ids:
+            self.remove_one(job_id)
+        return job_ids
+
+    def remove_one(self, job_uuid):
+        """删除指定任务"""
+        return self.scheduler.remove_job(job_uuid)
+
+    def pause_one(self, job_uuid):
+        """暂停指定任务"""
+        return self.scheduler.pause_job(job_uuid)
+
+    def resume_one(self, job_uuid):
+        """恢复指定任务"""
+        return self.scheduler.resume_job(job_uuid)
+
+    def pause_all(self):
+        """暂停全部任务"""
+        return self.scheduler.pause()
+
+    def resume_all(self):
+        """恢复全部任务"""
+        return self.scheduler.resume()
+
+    @classmethod
+    def create_job_by_hour(cls, run_date, ins_id):
+        """
+        定时执行
+        run_date: 执行时间(%Y-%m-%d %H:%M:%S)
+        ins_id: 数据id
+        """
+
+    # str_task_id = str(uuid.uuid4())
+    # str_item = mdb.get_one_by_id('UserEventStrategy', ins_id)
+    # str_task_id_list = str_item.get('str_task_id_list', [])
+    # methods.debug_log('ApsClient.create_job_by_hour', f"str_task_id_list: {str_task_id_list}")
+    # str_task_id_list.append(str_task_id)
+    # mdb.update_one_by_id('UserEventStrategy', ins_id, {'str_task_id_list': str_task_id_list})
+    # run_date = datetime.datetime.strptime(run_date, "%Y-%m-%d %H:%M:%S")
+    # cls.scheduler.add_job(func=action_event_strategy, trigger='date', run_date=run_date, id=str_task_id,
+    #                       args=[ins_id, ])
+
+    @classmethod
+    def create_job_by_interval(cls, ins_id, start_date, end_date):
+        """
+        每小时执行
+        :param ins_id: 数据id
+        :param start_date: 开始时间("%Y-%m-%d %H:%M:%S")
+        :param end_date: 结束时间("%Y-%m-%d %H:%M:%S")
+        :return:
+        """
+
+    # str_task_id = str(uuid.uuid4())
+    # str_item = mdb.get_one_by_id('UserEventStrategy', ins_id)
+    # str_task_id_list = str_item.get('str_task_id_list', [])
+    # methods.debug_log('ApsClient.create_job_by_interval', f"str_task_id_list: {str_task_id_list}")
+    # str_task_id_list.append(str_task_id)
+    # mdb.update_one_by_id('UserEventStrategy', ins_id, {'str_task_id_list': str_task_id_list})
+    # cls.scheduler.add_job(func=action_event_strategy, trigger='interval', id=str_task_id, hours=1,
+    #                       start_date=start_date, end_date=end_date, args=[ins_id, ])
+
+    @classmethod
+    def create_job_by_mon(cls, hour, minute, end_date, ins_id):
+        """
+        指定每周一固定时间执行
+        :param hour: 指定时
+        :param minute: 指定分
+        :param end_date: 结束时间("%Y-%m-%d %H:%M:%S")
+        :param ins_id: 数据id
+        :return:
+        """
+
+    # end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
+    # cls.scheduler.add_job(func=action_event_strategy, trigger='cron', day_of_week='mon', id=str(uuid.uuid4()),
+    #                       hour=hour, minute=minute, end_date=end_date, args=[ins_id, ])
+
+    @classmethod
+    def create_job_by_week(cls, _args, day_of_week, hour, minute, start_date, instance_id):
+        """每周"""
+
+    # cron_id = str(uuid.uuid4())
+    # cls.scheduler.add_job(send_charts, 'cron', day_of_week=day_of_week, id=cron_id, hour=hour,
+    #                       minute=minute,
+    #                       start_date=start_date, args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_month(cls, _args, month, day, hour, minute, start_date, instance_id):
+        """每月"""
+
+    # cron_id = str(uuid.uuid4())
+    #
+    # cls.scheduler.add_job(send_charts, 'cron', month=month, day=day, id=cron_id, hour=hour,
+    #                       minute=minute, start_date=start_date, args=_args, )
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_day(cls, _args, hour, minute, start_date, instance_id):
+        """每天"""
+
+    # cron_id = str(uuid.uuid4())
+    #
+    # cls.scheduler.add_job(send_charts, 'cron', id=cron_id, hour=hour, start_date=start_date,
+    #                       minute=minute, args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_hours(cls, _args, minute, start_date, instance_id):
+        """每小时"""
+
+    # cron_id = str(uuid.uuid4())
+    # cls.scheduler.add_job(send_charts, 'cron', id=cron_id,
+    #                       minute=minute, start_date=start_date, args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_n_day(cls, _args, day, hour, minute, start_date, instance_id):
+        """n天"""
+
+    # cron_id = str(uuid.uuid4())
+    # cls.scheduler.add_job(send_charts, 'interval', id=cron_id,
+    #                       days=day, hours=hour, minutes=minute, start_date=start_date,
+    #                       args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_n_hour(cls, _args, hour, minute, start_date, instance_id):
+        """n小时"""
+
+    # cron_id = str(uuid.uuid4())
+    # cls.scheduler.add_job(send_charts, 'interval', id=cron_id, hours=hour, minutes=minute,
+    #                       start_date=start_date, args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_n_minute(cls, _args, minute, start_date, instance_id):
+        """n分钟"""
+
+    # cron_id = str(uuid.uuid4())
+    # cls.scheduler.add_job(send_charts, 'interval', id=cron_id, minutes=minute, start_date=start_date,
+    #                       args=_args)
+    # cls.save_job_id(instance_id, cron_id)
+    # cls.scheduler.shutdown()
+
+    @classmethod
+    def create_job_by_n_minute_alarm(cls, minute, start_date, end_date):
+        """告警收敛"""
+    # cls.scheduler.add_job(early_count, 'interval', id=str(uuid.uuid4()), minutes=minute, start_date=start_date,
+    #                       end_date=end_date)
+    # cls.scheduler.shutdown()
+
+
+if __name__ == '__main__':
+    # --- init ---
+    aps = APS(db_type='mongo', db_host='192.168.30.13', db_port=7030,
+              username='admin', password='admin', database='vms', collection='LoopTask')
+
+    # --- 并不支持client方式测试 ---
+    pass

+ 189 - 0
3rdparty/xpip/zip_by_pyminizip.py

@@ -0,0 +1,189 @@
+# update: 2021-5-14-23
+from libraries import base_original as std
+from Crypto.Cipher import AES
+import base64
+import pyminizip
+import shutil
+import os
+
+
+class _AES(object):
+	def __init__(self, key='7486E0264E999881D4EF7BEEDF05A9F7', model='ECB', iv='',
+	             code_type='utf-8'):
+		"""
+		code_type: utf-8/gbk
+		"""
+		self.code_type = code_type
+		self.model = {'ECB': AES.MODE_ECB, 'CBC': AES.MODE_CBC}[model]
+		self.key = self.replenish(key)
+
+		# --- create aes object ---
+		if model == 'ECB':
+			self.aes = AES.new(self.key, self.model)
+		elif model == 'CBC':
+			self.aes = AES.new(self.key, self.model, iv)
+
+	def replenish(self, block, block_size=16):
+		"""block_size: AES key must be either 16, 24, or 32 bytes long"""
+		block = block.encode(self.code_type)
+		while len(block) % block_size != 0:
+			block += b'\x00'
+		return block
+
+	def encrypt(self, text):
+		text = self.replenish(text)
+		encrypt_text = self.aes.encrypt(text)
+		return base64.encodebytes(encrypt_text).decode().strip()
+
+	def decrypt(self, text):
+		text = base64.decodebytes(text.encode(self.code_type))
+		decrypt_text = self.aes.decrypt(text)
+		return decrypt_text.decode(self.code_type).strip('\0')
+
+
+def text_to_aes(text, key='YourPassword'):
+	"""aes加密"""
+	aes = _AES(key=key)
+	return std.text_to_b64(aes.encrypt(text))
+
+
+def aes_to_text(text, key='YourPassword'):
+	"""aes解密"""
+	aes = _AES(key=key)
+	return aes.decrypt(std.b64_to_text(text))
+
+
+def file_to_zip_v2_2(file_path, output_dir, key='<YourPassword@2021>', compress_level=1):
+	"""file转zip(不删除加密文件,并输出到指定目录)"""
+
+	# --- check parameter ---
+	if not os.path.isfile(file_path):
+		return dict(code=1, details='parameter error!')
+
+	# --- check parameter ---
+	if not os.path.isdir(output_dir):
+		return dict(code=2, details='parameter error!')
+
+	# --- check file name ---
+	file_name = file_path.split('/')[-1]
+	is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+	if is_encrypted:
+		shutil.copy(file_path, f"{output_dir}/{file_name}")
+		return dict(code=3, details='parameter error!')
+
+	# --- create name file ---
+	# dir_path = '/'.join(file_path.split('/')[:-1])
+	file_name_aes = text_to_aes(file_name, key=key)
+	file_name_md5 = std.byte_to_md5(std.text_to_byte(file_name_aes))
+	name_file_path = f"{output_dir}/{file_name_md5[-4:]}"
+	with open(name_file_path, 'w') as f1:
+		f1.write(file_name_aes)
+
+	# --- rename file ---
+	file_md5 = std.get_big_file_md5(file_path)
+	# data_file_path = f"{dir_path}/{file_md5}"
+	# os.rename(file_path, data_file_path)
+	data_file_path = f"{output_dir}/{file_md5}"
+	shutil.copy(file_path, data_file_path)
+
+	# --- define zip name ---
+	zip_path = f"{output_dir}/{file_md5}.{file_name_md5[-4:]}"
+
+	try:
+		# --- writing ---
+		"""
+		Args:
+			1. src file LIST path (list)
+			2. src file LIST prefix path (list) or []
+			3. dst file path (string)
+			4. password (string) or None (to create no-password zip)
+			5. compress_level(int) between 1 to 9, 1 (more fast) <---> 9 (more compress)
+			6. optional function to be called during processing which takes one argument, the count of how many files have been compressed
+		"""
+		pyminizip.compress_multiple([data_file_path, name_file_path], ['', ''], zip_path, key, compress_level)
+
+		# --- remove file ---
+		os.remove(data_file_path)
+		os.remove(name_file_path)
+		return dict(code=0, details='ended.')
+
+	except Exception as exception:
+
+		# --- revert ---
+		os.remove(name_file_path)
+		os.remove(data_file_path)
+
+		import traceback
+		print(traceback.format_exc())
+		return dict(code=-1, details=f"{traceback.format_exc()}")
+
+
+def zip_to_file_v2_2(file_path, key='<YourPassword>', no_path=True):
+	"""zip转file
+	todo(不删除加密文件,并输出到指定目录)"""
+
+	# --- check parameter ---
+	if not os.path.isfile(file_path):
+		return dict(code=1, details='parameter error!')
+
+	# --- check again ---
+	file_name = file_path.split('/')[-1]
+	is_encrypted = len(file_name) == 37 and file_name[32] == '.'
+	if not is_encrypted:
+		return dict(code=2, details='parameter error!')
+
+	# --- record ---
+	dir_path = '/'.join(file_path.split('/')[:-1])
+	names = os.listdir(dir_path)
+	before_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+	try:
+		# --- writing ---
+		"""
+		Args:
+			1. src file path (string)
+			2. password (string) or None (to unzip encrypted archives)
+			3. dir path to extract files or None (to extract in a specific dir or cwd)
+			4. withoutpath (exclude path of extracted)
+		"""
+		pyminizip.uncompress(file_path, key, dir_path, no_path)
+
+		# --- record ---
+		names = os.listdir(dir_path)
+		after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+
+		# --- check ---
+		temp_file = list(after_files - before_files)
+		if len(temp_file) != 2:
+			raise Exception('something is wrong!')
+
+		# --- get file name ---
+		raw_name = str()
+		for path in temp_file:
+			name = path.split('/')[-1]
+			if len(name) == 4:
+				with open(path, 'r') as f1:
+					raw_name = aes_to_text(f1.read(), key=key)
+				os.remove(path)
+
+		# --- rename ---
+		for path in list(after_files - before_files):
+			name = path.split('/')[-1]
+			if len(name) == 32:
+				os.rename(path, f"{dir_path}/{raw_name}")
+
+		# --- remove file ---
+		os.remove(file_path)
+		return dict(code=0, details='ended.')
+
+	except Exception as exception:
+
+		# --- revert ---
+		names = os.listdir(dir_path)
+		after_files = set(f"{dir_path}/{name}" for name in names if not os.path.isdir(f"{dir_path}/{name}"))
+		for path in list(after_files - before_files):
+			os.remove(path)
+
+		import traceback
+		print(traceback.format_exc())
+		return dict(code=-1, details=f"{traceback.format_exc()}")

+ 82 - 0
3rdparty/xplugin/hydra/plugin.py

@@ -0,0 +1,82 @@
+import subprocess
+
+
+class Plugin:
+    def __init__(self):
+        model_path = '/home/server/resources/DockerDependencies/2022/target/AlgorithmModels/FaceRecognition-arcface/'
+        model_path += 'arcface_r100_v1.onnx'
+        # model_path += 'MFR_glintr100.onnx'
+
+    @staticmethod
+    def run_hydra():
+        """执行命令"""
+        target = 'ssh://58.34.94.176:22'
+        username = 'server'
+        password = 'server@2000'
+
+        command = [
+            'hydra',
+            '-l', username,
+            '-p', password,
+            target,
+        ]
+
+        try:
+            result = subprocess.run(command, capture_output=True, text=True, check=True)
+            return result.stdout
+        except subprocess.CalledProcessError as e:
+            return e.stderr
+
+    def get_face_features_normalization_by_image_array(self, image_array):
+        """
+
+        :param image_array:
+        :return:
+        """
+        pass
+
+    @staticmethod
+    def normalize(embedding):
+        """特征数据格式化"""
+        embedding_norm = norm(embedding)
+        normed_embedding = embedding / embedding_norm
+        return normed_embedding
+
+    def get_face_features_normalization_by_image_array(self, image_array):
+        """获取特征"""
+
+        # --- debug ---
+        # methods.debug_log('FaceRecognitionEngine', f"m-92: size: {image_array.shape}")
+
+        # --- check todo 连乘得到像素值,旨在过滤低于112的尺寸图片 ---
+        # if np.prod(image_array.shape) < np.prod((112, 112, 3)):
+        #     return None
+
+        # --- check ---
+        if image_array is None:
+            return None
+
+        # --- check ---
+        if image_array.shape != (112, 112, 3):
+            # methods.debug_log('FaceRecognitionEngine', f"m-96: image resize before is {image_array.shape}")
+            image_array = cv2.resize(image_array, (112, 112))
+
+        if not isinstance(image_array, list):
+            image_array = [image_array]
+
+        for i, img in enumerate(image_array):
+            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+            img = np.transpose(img, (2, 0, 1))
+            image_array[i] = img.astype(np.float32)
+
+        image_array = np.stack(image_array)
+        net_out = self.onnx_session.run(self.outputs, {self.onnx_session.get_inputs()[0].name: image_array})
+        return self.normalize(net_out[0][0])
+
+
+if __name__ == '__main__':
+    # --- init ---
+    plugin = PLUGIN()
+
+    output = plugin.run_hydra()
+    print(output)

+ 260 - 0
3rdparty/xplugin/msscan/plugin.py

@@ -0,0 +1,260 @@
+"""
+# 安装
+apt-get install -y libpcap-dev masscan
+
+# --- test ssh ---
+masscan -p22 192.168.131.0/24 --rate=1000
+
+Discovered open port 22/tcp on 192.168.131.100                                 
+Discovered open port 22/tcp on 192.168.131.102                                 
+Discovered open port 22/tcp on 192.168.131.243                                 
+Discovered open port 22/tcp on 192.168.131.138                                 
+Discovered open port 22/tcp on 192.168.131.16                                  
+Discovered open port 22/tcp on 192.168.131.136                                 
+Discovered open port 22/tcp on 192.168.131.242                                 
+Discovered open port 22/tcp on 192.168.131.61                                  
+Discovered open port 22/tcp on 192.168.131.46                                  
+Discovered open port 22/tcp on 192.168.131.42                                  
+Discovered open port 22/tcp on 192.168.131.43                                  
+Discovered open port 22/tcp on 192.168.131.133                                 
+Discovered open port 22/tcp on 192.168.131.11                                  
+Discovered open port 22/tcp on 192.168.131.101                                 
+Discovered open port 22/tcp on 192.168.131.23                                  
+Discovered open port 22/tcp on 192.168.131.21                                  
+Discovered open port 22/tcp on 192.168.131.22                                  
+Discovered open port 22/tcp on 192.168.131.103                                 
+Discovered open port 22/tcp on 192.168.131.241                                 
+Discovered open port 22/tcp on 192.168.131.13                                  
+Discovered open port 22/tcp on 192.168.131.17                                  
+Discovered open port 22/tcp on 192.168.131.253                                 
+Discovered open port 22/tcp on 192.168.131.137                                 
+Discovered open port 22/tcp on 192.168.131.130                                 
+Discovered open port 22/tcp on 192.168.131.139                                 
+Discovered open port 22/tcp on 192.168.131.132
+
+# --- test 3网段 ---
+masscan 192.168.3.0/24 --rate=1000 --ports 0-65535
+
+# --- test sqlserver ---
+nmap -p 1433 192.168.131.0/24
+
+Starting Nmap 7.80 ( https://nmap.org ) at 2023-12-18 17:44 CST
+Nmap scan report for 192.168.131.1
+Host is up (0.00028s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.11
+Host is up (0.00021s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.12
+Host is up (0.00030s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.13
+Host is up (0.00015s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.16
+Host is up (0.00012s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.17
+Host is up (0.00028s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.20
+Host is up (0.00022s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.21
+Host is up (0.00013s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.22
+Host is up (0.00024s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.23
+Host is up (0.000024s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.41
+Host is up (0.018s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.42
+Host is up (0.011s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.43
+Host is up (0.031s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.45
+Host is up (0.00090s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.46
+Host is up (0.031s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.61
+Host is up (0.00056s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.62
+Host is up (0.00059s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.99
+Host is up (0.00021s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.100
+Host is up (0.000095s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.101
+Host is up (0.00014s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.102
+Host is up (0.00013s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.103
+Host is up (0.00015s latency).
+
+PORT     STATE    SERVICE
+1433/tcp filtered ms-sql-s
+
+Nmap scan report for 192.168.131.130
+Host is up (0.0049s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.131
+Host is up (0.012s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.132
+Host is up (0.015s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.133
+Host is up (0.011s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.136
+Host is up (0.014s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.137
+Host is up (0.0039s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.138
+Host is up (0.010s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.139
+Host is up (0.0043s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.241
+Host is up (0.00036s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.242
+Host is up (0.00038s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.243
+Host is up (0.00048s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.252
+Host is up (0.0023s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for 192.168.131.253
+Host is up (0.00016s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap scan report for _gateway (192.168.131.254)
+Host is up (0.0010s latency).
+
+PORT     STATE  SERVICE
+1433/tcp closed ms-sql-s
+
+Nmap done: 256 IP addresses (36 hosts up) scanned in 63.19 seconds
+
+"""

+ 59 - 0
3rdparty/xserver/xtcp.py

@@ -0,0 +1,59 @@
+import asyncio
+import struct
+
+class CustomProtocol(asyncio.Protocol):
+    clients = {}
+
+    def connection_made(self, client):
+        self.client = client
+        self.client_info = client.get_extra_info('peername')
+        print(f"Connection from {self.client_info}", flush=True)
+        CustomProtocol.clients[self.client_info] = client
+        self.on_connect()
+
+    def data_received(self, data):
+        asyncio.create_task(self.handle_data(data))
+
+    def connection_lost(self, exc):
+        print(f"Connection closed by {self.client_info}", flush=True)
+        if self.client_info in CustomProtocol.clients:
+            del CustomProtocol.clients[self.client_info]
+        self.on_disconnect()
+
+    def on_connect(self):
+        print(f"Connected to {self.client_info}", flush=True)
+
+    async def handle_data(self, data):
+        # Simulating a blocking operation with asyncio.run_in_executor
+        result = await asyncio.get_running_loop().run_in_executor(None, self.process_data, data)
+        self.client.write(result)
+
+    def process_data(self, data):
+        # Simulate a CPU-bound task
+        values = struct.unpack('!hh', data)
+        processed_values = (values[0] + 1, values[1] + 1)
+        return struct.pack('!hh', *processed_values)
+
+    def on_disconnect(self):
+        print(f"Disconnected from {self.client_info}", flush=True)
+
+    @classmethod
+    async def broadcast(cls, message):
+        data = struct.pack('!hh', *message)
+        for client in cls.clients.values():
+            client.write(data)
+        await asyncio.gather(*(client.drain() for client in cls.clients.values()))
+
+async def main():
+    loop = asyncio.get_running_loop()
+    server = await loop.create_server(
+        lambda: CustomProtocol(),
+        '0.0.0.0', 20917
+    )
+
+    async with server:
+        print("Server listening on 0.0.0.0:20917", flush=True)
+        await server.serve_forever()
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 61 - 0
3rdparty/xserver/xudp.py

@@ -0,0 +1,61 @@
+"""
+python3 /home/server/repositories/repositories/sri-project.demo-py/3rdparty/xserver/xudp.py
+"""
+import asyncio
+import struct
+
+
+# from concurrent.futures import ThreadPoolExecutor
+
+
+class CustomProtocol:
+    clients = {}
+
+    # executor = ThreadPoolExecutor()
+
+    def connection_made(self, transport):
+        self.transport = transport
+        self.peername = None  # UDP没有真正的连接,因此peername为空
+        print(f"UDP Server started", flush=True)
+
+    def datagram_received(self, data, addr):
+        asyncio.create_task(self.handle_data(data, addr))
+
+    def error_received(self, exc):
+        print(f"Error received: {exc}", flush=True)
+
+    async def handle_data(self, data, addr):
+        if len(data) < 4:
+            return  # Ensure complete data packet is received
+        loop = asyncio.get_running_loop()
+        values = await loop.run_in_executor(None, self.process_data, data)
+        # values = await loop.run_in_executor(CustomProtocol.executor, self.process_data, data)
+        print(f"Received values: {values} from {addr}", flush=True)
+
+        response = struct.pack('!hh', values[0] + 1, values[1] + 1)
+        self.transport.sendto(response, addr)
+
+    @staticmethod
+    def process_data(data):
+        values = struct.unpack('!hh', data)
+        print(f"Processing data: {values}", flush=True)
+        # Simulate some processing
+        return (values[0] + 1, values[1] + 1)
+
+
+async def main():
+    loop = asyncio.get_running_loop()
+    transport, protocol = await loop.create_datagram_endpoint(
+        lambda: CustomProtocol(),
+        local_addr=('127.0.0.1', 20917)
+    )
+
+    print("UDP Server listening on 127.0.0.1:20917", flush=True)
+    try:
+        await asyncio.Future()  # Keep running indefinitely
+    finally:
+        transport.close()  # Close the transport
+
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 92 - 0
sri-server-bg01/Dockerfile

@@ -0,0 +1,92 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Update apt:" \
+    && set -x \
+    && apt-get update \
+    && apt-get dist-upgrade -y \
+    && apt-get install -y \
+        wget unzip git apt-utils
+
+RUN echo "Install Python v3.8:" \
+    && set -x \
+#    && apt-get dist-upgrade -y \
+#    && apt-get update \
+    && apt-get install -y  \
+        python3-dev \
+        python3-pip \
+        python3-setuptools \
+        python3-wheel \
+    && pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
+    && pip3 config set global.extra-index-url https://pypi.doubanio.com/simple \
+    && pip3 config set global.extra-index-url https://mirrors.163.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://mirrors.cloud.tencent.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://mirror.baidu.com/pypi/simple \
+    && pip3 install --upgrade --quiet pip setuptools \
+    && python3 --version
+
+RUN echo "Install OpenCV v4.2.0:" \
+    && set -x \
+    && apt-get update \
+    && apt-get install -y libopencv-dev python3-opencv \
+    && python3 -c "import cv2; print(cv2.__version__)"
+
+# --- set ffmpeg ---
+RUN apt-get install --no-install-recommends -y git ffmpeg supervisor
+
+# --- install requirements ---
+RUN echo "Install Python Requirements:" \
+    && pip3 install --default-timeout=1800 --no-cache-dir \
+        # --- for base --- \
+        cython==3.0.0a9 \
+        pyinstaller==4.10 \
+        # --- for libraries --- \
+        pycrypto==2.6.1 \
+        paramiko==2.7.2 \
+        apscheduler==3.7.0 \
+        # --- for client --- \
+        requests==2.25.1 \
+        redis==3.5.3 \
+        pymongo==3.11.2 \
+        influxdb==5.3.1 \
+        pymysql==0.9.3 \
+        peewee==3.17.0 \
+        SQLAlchemy==1.4.30 \
+        # --- for server --- \
+        aiofiles==23.2.1 \
+        python-multipart==0.0.6 \
+        starlette==0.32.0 \
+        fastapi==0.108.0 \
+        fastapi-login==1.9.2 \
+        uvicorn==0.13.3 \
+        werkzeug==3.0.1 \
+        itsdangerous==1.1.0 \
+    && echo "End."
+
+        # --- for server --- \
+#        aiofiles==0.6.0 \
+#        python-multipart==0.0.5 \
+#        starlette==0.13.6 \
+#        fastapi==0.63.0 \
+#        fastapi-login==1.5.2 \
+#        supervisor==4.2.1 \
+#        uvicorn==0.13.3 \
+#        werkzeug==1.0.1 \
+#        itsdangerous==1.1.0 \
+
+
+#RUN echo "Install Torch v1.6.0:" \
+#    && pip3 install torch==1.6.0 torchvision==0.7.0 \
+#    && python3 -c "import torch; print(torch.__version__)"
+
+#RUN echo "Install Torch v1.9.0:" \
+#    && set -x \
+#    && apt-get install -y build-essential cmake \
+#    && pip3 install torch==1.9.0 torchvision==0.10.0 \
+#    && python3 -c "import torch; print(torch.__version__)"
+
+#COPY ./source/box.com/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl /opt/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl
+#RUN echo "Install onnxruntime:" \
+#    && pip3 install numpy==1.19.4 \
+#    && pip3 install /opt/onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl \
+#    && echo "End."

+ 37 - 0
sri-server-bg01/README-usage.bash

@@ -0,0 +1,37 @@
+## NOTE
+
+# 一、构建操作
+# 构建启动
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/casperz.py-project/project-fastapi-bg" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker-compose --file compose.yml logs --follow
+# 调试
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/casperz.py-project/project-fastapi-bg" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker exec -it sri_module_agent_001 bash
+# 强制构建并运行
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/casperz.py-project/project-fastapi-bg" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml build --no-cache \
+&& sudo docker-compose --file compose.yml up --detach \
+&& sudo docker-compose --file compose.yml logs --follow
+# 重启
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/casperz.py-project/project-fastapi-bg" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach \
+&& sudo docker-compose --file compose.yml logs --follow
+
+# 二、日常调试命令
+sudo chmod -R 777 /home/ubuntu/repositories/repositories
+sudo chmod -R 777 /home/server/repositories/repositories
+sudo docker restart sri-module-bg01 && sudo docker logs -f sri-module-bg01

+ 125 - 0
sri-server-bg01/README-usage.md

@@ -0,0 +1,125 @@
+#### usage
+```
+sudo docker restart fra-component-interface && sudo docker logs -f fra-component-interface
+```
+```
+# --- 试运行 ---
+echo "Test-Run-And-Check-Build:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker logs --follow test-component-interface
+```
+```
+# --- 试运行 ---
+echo "Test-Run-And-Check-Build:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker exec -it test-component-interface bash
+```
+```
+# --- 试运行 ---
+echo "Test-Run:" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms/component-interface" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach \
+&& sudo docker-compose --file compose_test.yml logs --follow
+```
+```
+# --- 打包方式运行(压缩) ---
+echo "Wrap-Application:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --tty \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --name fra-component-interface \
+    fra-component-interface:u20 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/component-interface/run-exe-wrap.sh
+```
+```
+# --- 打包方式运行(测试) ---
+echo "Run-Test:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --tty \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --name fra-component-interface \
+    ubuntu:20.04 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/component-interface/run-exe.sh
+```
+```
+# --- 构建镜像并上传镜像仓库(构建) ---
+echo "step-1: 收集代码" \
+&& project_path="/home/server/projects/taiwuict/cscec-8bur-vms" \
+&& cd ${project_path} \
+&& sudo rm -rf build \
+&& sudo mkdir build \
+&& cd build \
+&& sudo cp -rf ../supplement-python . \
+&& sudo cp -rf ../component-interface . \
+&& sudo rm -rf component-interface/__pycache__ \
+&& sudo rm -rf component-interface/test \
+&& sudo rm -rf component-interface/*.tar \
+&& echo "step-2: 编译源码" \
+&& sudo docker run \
+    --tty \
+    --volume /home:/home \
+    fra-component-interface:u20 \
+    /bin/bash /home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface/run-c.sh \
+&& echo "step-3: 制作镜像" \
+&& sudo docker build \
+    --tag fra-component-interface:new \
+    --file component-interface/build.Dockerfile \
+    --no-cache \
+    --rm \
+    . \
+&& echo "End."
+```
+```
+# --- 容器方式运行(测试) ---
+echo "Run-Test:" \
+&& sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --detach \
+    --env TZ=Asia/Shanghai \
+    --volume /home:/home \
+    --publish 8891:8000 \
+    --network node_network \
+    --restart always \
+    --name fra-component-interface \
+    fra-component-interface:new \
+&& sudo docker logs -f fra-component-interface
+```
+```
+# --- 构建镜像并上传镜像仓库(上传) ---
+echo "Upload-Image:" \
+&& locale_name="fra-component-interface:new" \
+&& remote_name_1="casperz/fra-component-interface:arm64" \
+&& remote_name_2="casperz/fra-component-interface:amd64" \
+&& bash /home/server/resources/HostNecessities/2022/scripts/dockerhub_login.sh \
+&& sudo docker tag ${locale_name} ${remote_name_1} \
+&& sudo docker push ${remote_name_1}
+```
+```
+# --- 启动容器(下载) ---
+sudo docker rm -f fra-component-interface \
+&& sudo docker run \
+    --detach \
+    --env TZ=Asia/Shanghai \
+    --publish 8891:8000 \
+    --network node_network \
+    --restart always \
+    --name fra-component-interface \
+    casperz/fra-component-interface:arm64 \
+&& sudo docker logs -f fra-component-interface
+```

+ 19 - 0
sri-server-bg01/README-usage.txt

@@ -0,0 +1,19 @@
+## NOTE
+
+# 一、构建镜像
+# 构建并运行
+echo "BEGIN:" \
+&& project_path="/home/server/repositories/repositories/sri-robot.cn/project.mccbts-ar-py/module-backend-fastapi" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose_test.yml down \
+&& sudo docker-compose --file compose_test.yml up --detach --build \
+&& sudo docker logs --follow sri-module-backend-fastapi
+
+# 二、日常调试命令
+sudo docker restart sri-module-backend-fastapi && sudo docker logs -f sri-module-backend-fastapi
+
+# 三、在win上运行命令
+# 1
+cd module-backend-fastapi
+# 2
+python main.py

+ 200 - 0
sri-server-bg01/api/api.py

@@ -0,0 +1,200 @@
+from starlette.formparsers import MultiPartParser as StartletteMultiPartParser
+from fastapi import APIRouter, Request, Response, Depends
+from fastapi.responses import FileResponse
+from key import v1 as key_v1
+
+from hub import methods, Global
+import importlib
+
+router = APIRouter()
+
+config_dict = {
+
+    'v6': {
+
+        # --- 用户管理 ---
+        1001: 'v6.code1000.code1001',  # 新增用户
+        1002: 'v6.code1000.code1002',  # 查询用户列表(分页)
+        1003: 'v6.code1000.code1003',  # 删除指定用户
+        1004: 'v6.code1000.code1004',  # 禁用指定用户
+        1005: 'v6.code1000.code1005',  # 启用指定用户
+        1006: 'v6.code1000.code1006',  # 获取指定用户详情
+        1007: 'v6.code1000.code1007',  # 修改指定用户信息
+
+        # --- 车辆管理 ---
+        2001: 'v6.code2000.code2001',  # 新增车辆
+        2002: 'v6.code2000.code2002',  # 查询车辆列表(分页)
+        2003: 'v6.code2000.code2003',  # 修改指定车辆信息
+        2004: 'v6.code2000.code2004',  # 禁止指定车辆远程操作
+        2005: 'v6.code2000.code2005',  # 允许指定车辆远程操作
+        2006: 'v6.code2000.code2006',  # 删除指定作业车辆
+        2007: 'v6.code2000.code2007',  # 获取指定作业车辆详情
+
+        # --- 关于日志 ---
+        3001: 'v6.code3000.code3001',  # 查询驾驶人员操作记录列表
+        3002: 'v6.code3000.code3002',  # 下载指定驾驶人员操作日志
+    }
+
+}
+
+methods_dict = {}  # {<tag>: {<method>: <path>}}
+
+
+def _get_method_by_code(code, tag):
+    """
+    通过code获取method(不调用不加载)
+    """
+    # --- check ---
+    if tag not in methods_dict:
+        methods_dict[tag] = {}
+
+    # --- check ---
+    if code in methods_dict.get(tag):
+        return methods_dict.get(tag).get(code)
+
+    # --- get method ---
+    method_path = config_dict.get(tag).get(code)
+    tag, file_name, method_name = method_path.split('.')
+    script = importlib.import_module(f"api.{tag}.{file_name}")
+    method = getattr(script, method_name)
+
+    # --- set method ---
+    methods_dict[tag][code] = method
+    return method
+
+
+@router.post('/{tag}/api')
+async def post(tag: str, request: Request, response: Response, user: dict = Depends(key_v1.login_required)):
+    """"""
+
+    # --- check ---
+    if not user.get('skip_is'):
+        response.headers['authorization'] = key_v1.get_token_by_user(user)
+
+    # --- get sources ---
+    sources = await request.json()
+    code = int(sources.get('code'))
+    page = sources.get('page', 1)
+    size = sources.get('size', 10)
+    ban_keys = ['code']
+
+    # --- set global args ---
+    sources = {key: val for key, val in sources.items() if key not in ban_keys}
+    sources['g_user_id'] = user.get('uid')
+    sources['g_user_name'] = user.get('username')
+    sources['g_user_pass'] = user.get('password')
+    sources['g_role_id'] = user.get('role_id')
+
+    # --- call action ---
+    try:
+        run_at = methods.now_ts()
+        method = _get_method_by_code(code=code, tag=tag)
+        result = await method(**sources)
+        methods.debug_log(f"api.post.95", f"#{tag}/{code}: use time {round(methods.now_ts() - run_at, 2)}s")
+
+        # --- check ---
+        if code in [
+            # 8201,  # 访客记录,列表
+        ] and result.__class__.__name__ == 'dict':
+            return dict(
+                code=result.get('code'),
+                data=result.get('data', [])[(page - 1) * size: page * size],
+                page=page,
+                size=size,
+                total=len(result.get('data', [])),
+            )
+        elif result.__class__.__name__ in ['FileResponse', 'dict']:
+            return result
+        else:
+            return dict(data=result, type=result.__class__.__name__)
+
+    except Exception as exception:
+
+        methods.debug_log('api.post.115', f"#tag: {tag}, #code: {code}")
+        methods.debug_log('api.post.115', f"#exception: {exception}")
+        methods.debug_log('api.post.115', f"#traceback: {methods.trace_log()}")
+        return dict(code=-1, data=[], message=f"something is wrong. [{exception.__class__.__name__}]",
+                    details=f"{methods.trace_log()}")
+
+
+@router.put('/actions')
+async def upload(request: Request, user: dict = Depends(key_v1.login_required)):
+    try:
+        # --- get sources ---
+        sources = dict()
+        parser = StartletteMultiPartParser(request.headers, request.stream())
+        params = await parser.parse()
+        # sources['params'] = params
+
+        # --- set sources --- fromdata列表处理
+        for key, value in params.multi_items():
+
+            # methods.debug_log('api.upload.137', f"#key: {key}, #value: {value}")
+            # --- check list ---
+            if key[-4:] == 'list' and key not in sources:
+                sources[key] = list()
+            if key[-4:] == 'list':
+                sources[key].append(value)
+
+            # --- make list ---
+            else:
+                if key in sources:
+                    sources[key] = [sources[key]]
+                    sources[key].append(value)
+                else:
+                    sources[key] = value
+            # methods.debug_log('actions.put', f"m-201: sources: {sources}")
+
+        # --- clean ---
+        tag = sources.get('tag', 'v1')
+        code = int(sources.get('code'))
+        del sources['tag']
+        del sources['code']
+
+        # --- set sources ---
+        sources['g_user_id'] = user.get('uid')
+        sources['g_user_name'] = user.get('username')
+        sources['g_user_pass'] = user.get('password')
+        sources['g_role_id'] = user.get('role_id')
+
+        # --- set files ---
+        files = dict()
+        for key, value in sources.items():
+            if value.__class__.__name__ == 'UploadFile':
+                files[key] = value
+        for key, value in files.items():
+            del sources[key]
+        sources['files'] = files
+
+        # --- call action ---
+        return await _get_method_by_code(code=code, tag=tag)(**sources)
+
+    except Exception as exception:
+
+        methods.debug_log('api.upload.176', f"#exception: {exception}")
+        methods.debug_log('api.upload.176', f"#traceback: {methods.trace_log()}")
+        return dict(code=-1, message=f"something is wrong. [{exception.__class__.__name__}]",
+                    details=f"{methods.trace_log()}")
+
+
+@router.get('/{tag}/api')
+async def download(request: Request, response: Response, tag: str):
+    try:
+        # --- get ---
+        params = request.query_params
+        # methods.debug_log('api.download.236', f"#params: {params}")
+        method = _get_method_by_code(code=params.get('code'), tag=tag)
+        result = await method(**params)
+
+        # --- 是否弹框 ---
+        if True:
+            return FileResponse(result.get('file_path'),
+                                media_type="application/octet-stream", filename=result.get('file_name'))
+        else:
+            return FileResponse(result.get('file_path'))
+
+    except Exception as exception:
+        methods.debug_log('api.download.249', f"#exception: {exception}")
+        methods.debug_log('api.download.249', f"#traceback: {methods.trace_log()}")
+        return dict(code=-1, message=f"something is wrong. [{exception.__class__.__name__}]",
+                    details=f"{methods.trace_log()}")

+ 298 - 0
sri-server-bg01/api/v6/code1000.py

@@ -0,0 +1,298 @@
+from werkzeug.security import generate_password_hash
+from hub import methods, Global
+
+
+async def code1001(**sources):
+    """
+    新增用户
+    """
+    # --- check ---
+    if not sources.get('username'):
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    if sources.get('password'):
+        sources['password'] = generate_password_hash(sources.get('password'))
+    else:
+        sources['password'] = generate_password_hash('baosteel@2024')
+
+    # --- save ---
+    """
+    UserInfo: 用户信息表
+    UserInfo.username: 登录账号
+    UserInfo.password: 登录密码 (默认值:baosteel@2024)
+    UserInfo.role_type: 角色类型 (1: 超级管理员 2: 普通管理员 3: 普通用户) (默认值:3)
+    UserInfo.create_at: 创建时间
+    UserInfo.update_at: 修改时间
+    UserInfo.name: 姓名
+    UserInfo.cid: 身份证号
+    UserInfo.phone: 手机号
+    UserInfo.sex: 性别 (0:未设置 1:男 2:女)
+    UserInfo.department: 部门
+    UserInfo.wid: 工号
+    UserInfo.allow_at: 注册审批时间
+    UserInfo.state: 禁用状态 (0:激活 1:禁用)
+    """
+    data = {
+        'username': sources.get('username'),
+        'password': sources.get('password'),
+        'role_type': str(sources.get('role_type', 3)),
+        'name': sources.get('name'),
+        'cid': sources.get('cid'),
+        'phone': sources.get('phone'),
+        'sex': str(sources.get('sex')),
+        'department': sources.get('department'),
+        'wid': sources.get('wid'),
+        'state': '0',
+        'allow_at': methods.now_ts(),
+    }
+    uuid = Global.mdb.add('UserInfo', data)
+    return dict(code=0, data=uuid)
+
+
+async def code1002(**sources):
+    """
+    查询用户列表
+    """
+    # --- check ---
+    if not sources.get('page'):
+        return dict(code=1, details=f"something is wrong.")
+    elif not sources.get('size'):
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- debug ---
+    # methods.debug_log('code1000.code1002.82:', f"#role_type: {sources.get('role_type')}")
+
+    # --- fill d1 ---
+    d1 = list()
+    page = sources.get('page', 1)
+    size = sources.get('size', 10)
+    for item in Global.mdb.get_all('UserInfo', sort_field=[('allow_at', -1)]):
+
+        # --- check ---
+        if sources.get('name') and sources.get('name') not in str(item.get('name')):
+            continue
+
+        # --- check ---
+        if sources.get('phone') and str(sources.get('phone')) != str(item.get('phone')):
+            continue
+
+        # --- check ---
+        if not item.get('role_type'):
+            item['role_type'] = '3'
+
+        # --- check ---
+        if sources.get('role_type') and str(item.get('role_type')) not in sources.get('role_type'):
+            continue
+
+        # --- check ---
+        item['role_type'] = str(item.get('role_type'))
+        item['sex'] = str(item.get('sex'))
+        item['state'] = str(item.get('state'))
+
+        item['uuid'] = str(item.get('_id'))
+        item.pop('_id')
+        item.pop('password')
+        d1.append(item)
+
+    return dict(code=0, data=d1[(page - 1) * size: page * size], total=len(d1), page=page, size=size)
+
+
+async def code1003(**sources):
+    """
+    删除指定用户
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('UserInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- remove ---
+    Global.mdb.remove_one_by_id('UserInfo', uuid)
+    return dict(code=0, data=uuid)
+
+
+async def code1004(**sources):
+    """
+    禁用指定用户
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('UserInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- update ---
+    """
+    UserInfo: 用户信息表
+    UserInfo.username: 登录账号
+    UserInfo.password: 登录密码 (默认值:baosteel@2024)
+    UserInfo.role_type: 角色类型 (1: 超级管理员 2: 普通管理员 3: 普通用户) (默认值:3)
+    UserInfo.create_at: 创建时间
+    UserInfo.update_at: 修改时间
+    UserInfo.name: 姓名
+    UserInfo.cid: 身份证号
+    UserInfo.phone: 手机号
+    UserInfo.sex: 性别 (0:未设置 1:男 2:女)
+    UserInfo.department: 部门
+    UserInfo.wid: 工号
+    UserInfo.allow_at: 注册审批时间
+    UserInfo.state: 禁用状态 (0:激活 1:禁用)
+    """
+    update_dict = dict()
+    update_dict['state'] = 1
+    item = Global.mdb.update_one_by_id('UserInfo', uuid, update_dict, need_back=True)
+    return dict(code=0, data=item)
+
+
+async def code1005(**sources):
+    """
+    启用指定用户
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('UserInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- update ---
+    """
+    UserInfo: 用户信息表
+    UserInfo.username: 登录账号
+    UserInfo.password: 登录密码 (默认值:baosteel@2024)
+    UserInfo.role_type: 角色类型 (1: 超级管理员 2: 普通管理员 3: 普通用户) (默认值:3)
+    UserInfo.create_at: 创建时间
+    UserInfo.update_at: 修改时间
+    UserInfo.name: 姓名
+    UserInfo.cid: 身份证号
+    UserInfo.phone: 手机号
+    UserInfo.sex: 性别 (0:未设置 1:男 2:女)
+    UserInfo.department: 部门
+    UserInfo.wid: 工号
+    UserInfo.allow_at: 注册审批时间
+    UserInfo.state: 禁用状态 (0:激活 1:禁用)
+    """
+    update_dict = dict()
+    update_dict['state'] = 0
+    item = Global.mdb.update_one_by_id('UserInfo', uuid, update_dict, need_back=True)
+    return dict(code=0, data=item)
+
+
+async def code1006(**sources):
+    """
+    获取指定用户详情
+    """
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    item = Global.mdb.get_one_by_id('UserInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- fill ---
+    """
+    UserInfo: 用户信息表
+    UserInfo.username: 登录账号
+    UserInfo.password: 登录密码 (默认值:baosteel@2024)
+    UserInfo.role_type: 角色类型 (1: 超级管理员 2: 普通管理员 3: 普通用户) (默认值:3)
+    UserInfo.create_at: 创建时间
+    UserInfo.update_at: 修改时间
+    UserInfo.name: 姓名
+    UserInfo.cid: 身份证号
+    UserInfo.phone: 手机号
+    UserInfo.sex: 性别 (0:未设置 1:男 2:女)
+    UserInfo.department: 部门
+    UserInfo.wid: 工号
+    UserInfo.allow_at: 注册审批时间
+    UserInfo.state: 禁用状态 (0:激活 1:禁用)
+    """
+    item.pop('_id')
+    item['uuid'] = uuid
+
+    # --- check ---
+    if not item.get('role_type'):
+        item['role_type'] = '3'
+
+    # --- check ---
+    item['role_type'] = str(item.get('role_type'))
+    item['sex'] = str(item.get('sex'))
+    item['state'] = str(item.get('state'))
+    return dict(code=0, data=item)
+
+
+async def code1007(**sources):
+    """
+    修改指定用户信息
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check again ---
+    item = Global.mdb.get_one_by_id('UserInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- update UserCamera by camera_uuid ---
+    """
+    UserInfo: 用户信息表
+    UserInfo.username: 登录账号
+    UserInfo.password: 登录密码 (默认值:baosteel@2024)
+    UserInfo.role_type: 角色类型 (1: 超级管理员 2: 普通管理员 3: 普通用户) (默认值:3)
+    UserInfo.create_at: 创建时间
+    UserInfo.update_at: 修改时间
+    UserInfo.name: 姓名
+    UserInfo.cid: 身份证号
+    UserInfo.phone: 手机号
+    UserInfo.sex: 性别 (0:未设置 1:男 2:女)
+    UserInfo.department: 部门
+    UserInfo.wid: 工号
+    UserInfo.allow_at: 注册审批时间
+    UserInfo.state: 禁用状态 (0:激活 1:禁用)
+    """
+    update_dict = dict()
+    update_dict['update_at'] = methods.now_ts()
+    if sources.get('username'):
+        update_dict['username'] = sources.get('username')
+    if sources.get('password'):
+        update_dict['password'] = generate_password_hash(sources.get('password'))
+    if sources.get('role_type'):
+        update_dict['role_type'] = str(sources.get('role_type'))
+    if sources.get('name'):
+        update_dict['name'] = sources.get('name')
+    if sources.get('cid'):
+        update_dict['cid'] = sources.get('cid')
+    if sources.get('phone'):
+        update_dict['phone'] = sources.get('phone')
+    if sources.get('sex'):
+        update_dict['sex'] = str(sources.get('sex'))
+    if sources.get('department'):
+        update_dict['department'] = sources.get('department')
+    if sources.get('wid'):
+        update_dict['wid'] = sources.get('wid')
+
+    # --- check ---
+    if type(item.get('role_type')) == int:
+        update_dict['role_type'] = str(item.get('role_type'))
+    if type(item.get('sex')) == int:
+        update_dict['sex'] = str(item.get('sex'))
+    if type(item.get('state')) == int:
+        update_dict['state'] = str(item.get('state'))
+
+    item = Global.mdb.update_one_by_id('UserInfo', uuid, update_dict, need_back=True)
+    return dict(code=0, data=item)

+ 240 - 0
sri-server-bg01/api/v6/code2000.py

@@ -0,0 +1,240 @@
+from hub import methods, Global
+
+
+async def code2001(**sources):
+    """
+    新增车辆
+    """
+    # --- check ---
+    if not sources.get('pid'):
+        return dict(code=1, details=f"something is wrong.")
+    elif not sources.get('type'):
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- check ---
+    count = Global.mdb.get_count('VehicleInfo', {'pid': sources.get('pid')})
+    if count != 0:
+        return dict(code=3, details=f"something is wrong.")
+
+    # --- save ---
+    """
+    VehicleInfo: 车辆信息表
+    VehicleInfo.pid: 车牌号
+    VehicleInfo.type: 车型号
+    VehicleInfo.host_address: 工控机地址
+    VehicleInfo.rtk_address: rtk地址
+    VehicleInfo.cpe_address: cpe地址
+    VehicleInfo.release_at: 出厂时期
+    VehicleInfo.state: 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+    VehicleInfo.permit_state: 遥操状态 (0:允许 1:禁用)
+    VehicleInfo.update_at: 更新时间
+    """
+    data = {
+        'pid': sources.get('pid'),
+        'type': sources.get('type'),
+        'host_address': sources.get('host_address'),
+        'rtk_address': sources.get('rtk_address'),
+        'cpe_address': sources.get('cpe_address'),
+        'release_at': sources.get('release_at'),
+        'update_at': methods.now_ts(),
+        'state': 1,
+        'permit_state': 0,
+    }
+    uuid = Global.mdb.add('VehicleInfo', data)
+    return dict(code=0, data=uuid)
+
+
+async def code2002(**sources):
+    """
+    查询车辆列表
+    """
+    # --- check ---
+    if not sources.get('page'):
+        return dict(code=1, details=f"something is wrong.")
+    elif not sources.get('size'):
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- fill d1 ---
+    d1 = list()
+    page = sources.get('page', 1)
+    size = sources.get('size', 10)
+    for item in Global.mdb.get_all('VehicleInfo', sort_field=[('update_at', -1)]):
+
+        # --- check ---
+        if sources.get('pid') and sources.get('pid') not in item.get('pid'):
+            continue
+
+        data = {
+            'uuid': str(item.get('_id')),
+            'pid': item.get('pid'),
+            'type': item.get('type'),
+            'host_address': item.get('host_address'),
+            'rtk_address': item.get('rtk_address'),
+            'cpe_address': item.get('cpe_address'),
+            'release_at': item.get('release_at'),
+            'state': item.get('state', 1),
+            'permit_state': item.get('permit_state', 0),
+        }
+        d1.append(data)
+    return dict(code=0, data=d1[(page - 1) * size: page * size], total=len(d1), page=page, size=size)
+
+
+async def code2003(**sources):
+    """
+    修改指定车辆信息
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- check ---
+    # count = Global.mdb.get_count('VehicleInfo', {'pid': sources.get('pid')})
+    # if count != 0:
+    #     return dict(code=3, details=f"something is wrong.")
+
+    # --- update ---
+    """
+    VehicleInfo: 车辆信息表
+    VehicleInfo.pid: 车牌号
+    VehicleInfo.type: 车型号
+    VehicleInfo.host_address: 工控机地址
+    VehicleInfo.rtk_address: rtk地址
+    VehicleInfo.cpe_address: cpe地址
+    VehicleInfo.release_at: 出厂时期
+    VehicleInfo.state: 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+    VehicleInfo.permit_state: 遥操状态 (0:允许 1:禁用)
+    VehicleInfo.update_at: 更新时间
+    """
+    update_dict = dict()
+    update_dict['update_at'] = methods.now_ts()
+    if sources.get('pid'):
+        update_dict['pid'] = sources.get('pid')
+    if sources.get('type'):
+        update_dict['type'] = sources.get('type')
+    if sources.get('host_address'):
+        update_dict['host_address'] = sources.get('host_address')
+    if sources.get('rtk_address'):
+        update_dict['rtk_address'] = sources.get('rtk_address')
+    if sources.get('cpe_address'):
+        update_dict['cpe_address'] = sources.get('cpe_address')
+    if sources.get('release_at'):
+        update_dict['release_at'] = sources.get('release_at')
+    Global.mdb.update_one_by_id('VehicleInfo', uuid, update_dict)
+    return dict(code=0, data=uuid)
+
+async def code2004(**sources):
+    """
+    禁止指定车辆远程操作
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- update ---
+    """
+    VehicleInfo: 车辆信息表
+    VehicleInfo.pid: 车牌号
+    VehicleInfo.type: 车型号
+    VehicleInfo.host_address: 工控机地址
+    VehicleInfo.rtk_address: rtk地址
+    VehicleInfo.cpe_address: cpe地址
+    VehicleInfo.release_at: 出厂时期
+    VehicleInfo.state: 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+    VehicleInfo.permit_state: 遥操状态 (0:允许 1:禁用)
+    VehicleInfo.update_at: 更新时间
+    """
+    update_dict = dict()
+    update_dict['permit_state'] = 1
+    item = Global.mdb.update_one_by_id('VehicleInfo', uuid, update_dict, need_back=True)
+    return dict(code=0, data=item)
+
+async def code2005(**sources):
+    """
+    允许指定车辆远程操作
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- update ---
+    """
+    VehicleInfo: 车辆信息表
+    VehicleInfo.pid: 车牌号
+    VehicleInfo.type: 车型号
+    VehicleInfo.host_address: 工控机地址
+    VehicleInfo.rtk_address: rtk地址
+    VehicleInfo.cpe_address: cpe地址
+    VehicleInfo.release_at: 出厂时期
+    VehicleInfo.state: 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+    VehicleInfo.permit_state: 遥操状态 (0:允许 1:禁用)
+    VehicleInfo.update_at: 更新时间
+    """
+    update_dict = dict()
+    update_dict['permit_state'] = 0
+    item = Global.mdb.update_one_by_id('VehicleInfo', uuid, update_dict, need_back=True)
+    return dict(code=0, data=item)
+
+async def code2006(**sources):
+    """
+    删除指定车辆
+    """
+    # --- check ---
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    # --- check ---
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- clean ---
+    Global.mdb.remove_one_by_id('VehicleInfo', uuid)
+    return dict(code=0, data=uuid)
+
+async def code2007(**sources):
+    """
+    获取指定作业车辆详情
+    """
+    uuid = sources.get('uuid')
+    if not uuid:
+        return dict(code=1, details=f"something is wrong.")
+
+    item = Global.mdb.get_one_by_id('VehicleInfo', uuid)
+    if not item:
+        return dict(code=2, details=f"something is wrong.")
+
+    # --- fill ---
+    """
+    VehicleInfo: 车辆信息表
+    VehicleInfo.pid: 车牌号
+    VehicleInfo.type: 车型号
+    VehicleInfo.host_address: 工控机地址
+    VehicleInfo.rtk_address: rtk地址
+    VehicleInfo.cpe_address: cpe地址
+    VehicleInfo.release_at: 出厂时期
+    VehicleInfo.state: 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+    VehicleInfo.permit_state: 遥操状态 (0:允许 1:禁用)
+    VehicleInfo.update_at: 更新时间
+    """
+    item.pop('_id')
+    item['uuid'] = uuid
+    return dict(code=0, data=item)

+ 131 - 0
sri-server-bg01/api/v6/code3000.py

@@ -0,0 +1,131 @@
+from hub import methods, Global
+
+
+async def code3001(**sources):
+    """
+    查询驾驶人员操作记录列表
+    """
+    # # --- check ---
+    # if not sources.get('page'):
+    #     return dict(code=1, details=f"something is wrong.")
+    # elif not sources.get('size'):
+    #     return dict(code=2, details=f"something is wrong.")
+    #
+    # # --- fill d1 ---
+    # """
+    # VehicleLog: 车辆日志表
+    # VehicleLog.pid: 车牌号
+    # VehicleLog.start_time_at: 远程驾驶开始时间
+    # VehicleLog.end_time_at: 远程驾驶结束时间
+    # VehicleLog.driver_name: 驾驶员
+    # VehicleLog.cockpit_name: 驾驶舱
+    # VehicleLog.create_at: 创建时间
+    # """
+    # d1 = list()
+    page = sources.get('page', 1)
+    size = sources.get('size', 10)
+    # for item in Global.mdb.get_all('VehicleInfo', sort_field=[('create_at', -1)]):
+    #
+    #     # --- check ---
+    #     if sources.get('pid') and sources.get('pid') not in item.get('pid'):
+    #         continue
+    #
+    #     # --- check ---
+    #     if sources.get('driver_name') and sources.get('driver_name') not in item.get('driver_name'):
+    #         continue
+    #
+    #     data = {
+    #         'uuid': str(item.get('_id')),
+    #         'pid': item.get('pid'),
+    #         'start_time_at': methods.now_ts(),
+    #         'end_time_at': methods.now_ts(),
+    #         'driver_name': item.get('driver_name'),
+    #         'cockpit_name': item.get('cockpit_name'),
+    #     }
+    #     d1.append(data)
+    #
+    # return dict(code=0, data=d1[(page - 1) * size: page * size], total=len(d1), page=page, size=size)
+
+    d1 = [
+        {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '张三',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }, {
+            'uuid': '65dbe96949fbe311a3a01d30',
+            'pid': 'AA112233',
+            'start_time_at': methods.now_ts(),
+            'end_time_at': methods.now_ts() + (3600 * 8),
+            'driver_name': '李四',
+            'cockpit_name': '1号舱',
+        }
+    ]
+    return dict(code=0, data=d1[(page - 1) * size: page * size], total=len(d1), page=page, size=size)
+
+
+async def code3002(**sources):
+    """
+    下载指定驾驶人员操作日志
+    """
+    file_name = '2024-03-04.log'
+    file_path = f"/home/server/logs/{file_name}"
+    return {'file_path': file_path, 'file_name': file_name}

+ 24 - 0
sri-server-bg01/app.py

@@ -0,0 +1,24 @@
+"""
+注册路由
+"""
+from starlette.middleware.sessions import SessionMiddleware
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi import FastAPI, Depends
+from key.v1 import router as token_router
+from key.v1 import login_required
+from api.api import router as api_router
+
+app = FastAPI()
+app.add_middleware(SessionMiddleware, secret_key='casper.com@2021')
+app.include_router(token_router)
+app.include_router(api_router, dependencies=[Depends(login_required)],
+                   responses={404: {'description': 'Not found!'}})
+
+# --- 支持跨域访问 --- see: https://blog.csdn.net/qq_33801641/article/details/120540963
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],  # 允许访问的源
+    allow_credentials=True,  # 支持 cookie
+    allow_methods=["*"],  # 允许使用的请求方法
+    allow_headers=["*"],  # 允许携带的 Headers
+)

+ 22 - 0
sri-server-bg01/build.Dockerfile

@@ -0,0 +1,22 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Support Time Zone" \
+    && set -x \
+    && apt-get dist-upgrade  \
+    && apt-get update \
+    && apt-get install -y tzdata
+
+# --- put file ---
+COPY ./component-interface /opt/cscec-8bur-vms/component-interface
+COPY ./supplement-python /opt/cscec-8bur-vms/supplement-python
+
+# --- clean ---
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*Dockerfile
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*spec
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*md
+RUN rm -rf /opt/cscec-8bur-vms/component-interface/*sh
+
+# --- run file ---
+WORKDIR /opt/cscec-8bur-vms/component-interface
+CMD ["/bin/bash", "-c", "./main"]

+ 37 - 0
sri-server-bg01/compose.yml

@@ -0,0 +1,37 @@
+version: '3.5'
+services:
+
+    sri-module-bg01:
+
+        # --- building ---
+        image: sri-module-bg01:2024
+        build:
+            context: ./
+            dockerfile: ./Dockerfile
+        environment:
+            TZ: Asia/Shanghai
+
+        # --- binding ---
+        volumes:
+            - /home:/home
+        networks:
+            - sri_network
+        ports:
+            - "9101:9000"
+
+        # --- running ---
+        container_name: sri-module-bg01
+
+        # --- for debug ---
+#        working_dir: /home/server/repositories/repositories/casperz.py-project/project-fastapi-bg
+#        stdin_open: true
+#        tty: true
+
+        # --- for release ---
+        working_dir: /home/server/repositories/repositories/casperz.py-project/project-fastapi-bg
+        command: bash run.sh
+        restart: always
+
+networks:
+    sri_network:
+        external: true

+ 54 - 0
sri-server-bg01/cythonize.py

@@ -0,0 +1,54 @@
+"""
+用于python源码打包成so文件,python解释器运行main.py
+"""
+from distutils.core import setup
+from Cython.Build import cythonize
+import os
+
+py_dir = [
+    '/home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface',
+    '/home/server/projects/taiwuict/cscec-8bur-vms/build/supplement-python',
+]
+
+
+def get_file_path_list(dir_path, path_list=None):
+    if not path_list:
+        path_list = []
+    for path, folders, files in os.walk(dir_path):
+
+        for file_name in files:
+            if file_name[-2:] != 'py':
+                continue
+            if 'cythonize' in file_name:
+                continue
+            # if '__init__' in file_name:
+            #     continue
+            file_path = os.path.join(path, file_name)
+            if file_path in path_list:
+                continue
+            path_list.append(file_path)
+
+        for folder_name in folders:
+            get_file_path_list(os.path.join(path, folder_name), path_list)
+
+    return path_list
+
+
+py_list = []
+for dir in py_dir:
+    py_list += get_file_path_list(dir)
+print(py_list)
+
+# setup(ext_modules=cythonize(module_list=py_list,
+#                             compiler_directives=dict(c_string_encoding="utf-8", language_level=3)))
+
+# --- one by one ---
+for py_file in py_list:
+    setup(
+        name='fra',
+        package_dir={
+            'v3': '',
+            'base_original': '',
+        },
+        ext_modules=cythonize(module_list=[py_file],
+                              compiler_directives=dict(c_string_encoding="utf-8", language_level=3)))

+ 101 - 0
sri-server-bg01/default_data.py

@@ -0,0 +1,101 @@
+data = {
+    'enable': True,
+    'config': {
+        # --- 用户配置 ---
+        'Role&User': [
+            {
+                'role_info': {
+                    'role_type': 1,
+                    'role_name': '超级管理员',
+                    'switch_list': [
+                        'admin'
+                    ],
+                },
+                'users_info': [
+                    {
+                        'username': 'admin',
+                        'password': '123456',
+                    }
+                ],
+            },
+            # {
+            #     'role': {
+            #         'role_name': '普通管理员',
+            #         'role_acl': [
+            #             'WorkHome',
+            #             'shishi',
+            #             'delall',
+            #             'FollowList',
+            #             'deljilu',
+            #             'WhiteVul',
+            #             'WhitePolicy',
+            #             'Options',
+            #             'handleTime',
+            #             'Bank',
+            #             'BankManagement',
+            #             'About',
+            #         ],
+            #     },
+            #     'users': [
+            #         {
+            #             'username': 'admin',
+            #             'password': '123456',
+            #         }
+            #     ],
+            # },
+            # {
+            #     'role': {
+            #         'role_name': '驾驶员',
+            #         'role_acl': [
+            #             'WorkHome',
+            #             'shishi',
+            #             'About'
+            #         ],
+            #     },
+            #     'users': [
+            #         {
+            #             'username': 'user001',
+            #             'password': '123456',
+            #         }
+            #     ],
+            # }
+        ],
+        # --- 全局配置 ---
+        'GlobalVariable': {
+            'CameraConfig': {
+                # 海康 人脸摄像机 盛景广场6层 todo 存在现场抓拍图片需要该参数的问题
+                # 'camera_rtsp': 'rtsp://admin:DEVdev123@192.168.0.181:554/h264/ch1/main/av_stream',
+                # 'camera_ipv4': '192.168.0.181',
+                # 'camera_user': 'admin',
+                # 'camera_pass': 'DEVdev123',
+
+                # 海康 晨鸣大厦6层
+                # 'camera_rtsp': 'rtsp://admin:hik12345+@192.168.1.3:554/h264/ch1/main/av_stream',
+                # 'camera_ipv4': '192.168.1.3',
+                # 'camera_user': 'admin',
+                # 'camera_pass': 'hik12345+',
+
+                # 大华 400w
+                # 'camera_rtsp': 'rtsp://admin:DEVdev123@192.168.0.234:554/cam/realmonitor?channel=1&subtype=0',
+
+                # 海康 800w 晨鸣大厦 3th
+                # 'camera_rtsp': 'rtsp://admin:DEVdev123@10.8.20.132:554/h264/ch1/main/av_stream',
+
+                # 旷视 八局机关公司 位置1 4th
+                # 'camera_rtsp': 'rtsp://admin:qwertyuiop3@10.8.10.227:554/1/1',
+
+                # 海康 人脸摄像机 中建八局 第3现场 山东省公共卫生临床中心
+                # 'camera_rtsp': 'rtsp://admin:DEVdev123@192.168.15.191:554/h264/ch1/main/av_stream',
+                # 'camera_ipv4': '192.168.15.191',
+                # 'camera_user': 'admin',
+                # 'camera_pass': 'DEVdev123',
+
+                # 海康 人脸摄像机 中建八局 第4现场 奥东项目
+                'camera_rtsp': 'rtsp://admin:DEVdev123@192.168.1.64:554/h264/ch1/main/av_stream',
+                'camera_ipv4': '192.168.1.64',
+                'camera_user': 'admin',
+                'camera_pass': 'DEVdev123',
+            }
+        }
+    }
+}

+ 72 - 0
sri-server-bg01/default_data_insert.py

@@ -0,0 +1,72 @@
+from werkzeug.security import generate_password_hash
+from hub import methods, Global
+
+
+def default_data_insert():
+    # --- check ---
+    from default_data import data
+    if not data.get('enable'):
+        return
+    config = data.get('config')
+
+    # --- check UserRole&User ---
+    d1 = {
+        'role': [],
+        'user': [],
+    }
+    for item in config.get('Role&User'):
+
+        # --- insert role_info ---
+        role_info = item.get('role_info')
+        unique_dict = {
+            'role_type': role_info.get('role_type'),
+        }
+        mdb_role = Global.mdb.get_one('UserRole', unique_dict)
+
+        # --- check ---
+        if not mdb_role:
+            update_dict = {
+                'role_type': role_info.get('role_type'),
+                'role_name': role_info.get('role_name'),
+                'switch_list': role_info.get('switch_list'),
+                'create_at': methods.now_ts(),
+            }
+            rid = Global.mdb.update_one('UserRole', unique_dict, update_dict)
+            d1['role'].append(rid)
+        else:
+            rid = str(mdb_role.get('_id'))
+            d1['role'].append(rid)
+
+        # --- insert users_info ---
+        users_info = item.get('users_info')
+        for user in users_info:
+            unique_dict = {'username': user.get('username')}
+            # mdb_user = Global.mdb.get_one('User', unique_dict)
+            mdb_user = Global.mdb.get_one('UserInfo', unique_dict)
+
+            # --- check ---
+            if not mdb_user:
+                update_dict = {
+                    'password': generate_password_hash(user.get('password')),
+                    'role_type': role_info.get('role_type'),
+                    'create_at': methods.now_ts(),
+                }
+                # uid = Global.mdb.update_one('User', unique_dict, update_dict)
+                uid = Global.mdb.update_one('UserInfo', unique_dict, update_dict)
+                d1['user'].append(uid)
+            else:
+                uid = str(mdb_user.get('_id'))
+                d1['user'].append(uid)
+    methods.debug_log('default_data_insert.default_data_insert.60', f"#d1: {d1}")
+
+    # --- check ---
+    # unique_dict = {'name': 'CameraConfig'}
+    # item = Global.mdb.get_one('GlobalVariable', unique_dict)
+    # data = item.get('args', {})
+    # data['camera_rtsp'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_rtsp', '')
+    # data['camera_ipv4'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_ipv4', '')
+    # data['camera_user'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_user', '')
+    # data['camera_pass'] = config.get('GlobalVariable', {}).get('CameraConfig', {}).get('camera_pass', '')
+    # update_dict = {'args': data}
+    # Global.mdb.update_one('GlobalVariable', unique_dict, update_dict)
+    # methods.debug_log('default_data_insert.default_data_insert', f"m-68: {data}")

+ 35 - 0
sri-server-bg01/hub.py

@@ -0,0 +1,35 @@
+import sys
+import importlib
+
+sys.path.append('../3rdparty')
+
+methods = importlib.import_module(f"xlib")
+
+
+# camera_handle = importlib.import_module(f"libraries.base_external.camera_by_cv2")
+# numpy_method = importlib.import_module(f"libraries.base_external.data_by_numpy")
+
+
+class Global(object):
+    # --- 时序数据库 ---
+    # xdb = importlib.import_module(f"clients.db_influx").Client(host='fra-middleware-influx', port=8086,
+    #                                                            database='vms')
+
+    # --- 业务数据数据库 ---
+    # mdb = importlib.import_module(f"clients.db_mongo").Client(host='sri-thirdparty-mongo', port=27017, database='ar',
+    #                                                           username='admin', password='admin')
+    mdb = importlib.import_module(f"xclient.xmongo").Client(host='58.34.94.176', port=7030, database='bg',
+                                                            username='admin', password='admin')
+    # mysql = importlib.import_module(f"clients.db_maria2").Client(host='127.0.0.1', port=3306, database='ar',
+    #                                                              username='root', password='20221212!')
+    # mysql = importlib.import_module(f"xclient.db_maria2").Client(host='58.34.94.176', port=8806, database='hs',
+    #                                                              username='root', password='rootroot&123123')
+
+    # --- 缓存数据库 ---
+    # rdb = importlib.import_module(f"clients.db_redis").Client(db=0, host='fra-middleware-redis', port=6379)
+
+    # SSHClient = importlib.import_module(f"clients.l4_ssh_by_paramiko").Client
+
+    # local_api = importlib.import_module(f"apis.local.api").Api()
+
+    mccbts_agent = importlib.import_module(f"xapi.mccbts.api").API()

+ 158 - 0
sri-server-bg01/key/v1.py

@@ -0,0 +1,158 @@
+from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
+from werkzeug.security import check_password_hash
+from fastapi import APIRouter, Request, Response
+from fastapi import HTTPException, Header
+from fastapi.responses import JSONResponse
+from hub import methods, Global
+
+router = APIRouter()
+# mdb = Global.get_mongodb_client()
+# serializer = Serializer(secret_key='casper.com@2021', expires_in=86400 * 100)  # debug
+serializer = Serializer(secret_key='casper.com@2021', expires_in=86400)  # release
+
+
+def get_token_by_user(user):
+    """生成token"""
+    data = {
+        'id': user.get('uid'),
+        'username': user.get('username'),
+        'password': user.get('password'),
+    }
+    return serializer.dumps(data).decode('utf-8')
+
+
+@router.post('/v1/token')
+async def get_token(request: Request, response: Response):
+    """获取令牌"""
+
+    methods.debug_log('v1.get_token.28', f"#now at {methods.now_string()}, ip: {request.client.host}")
+
+    # --- check key --- # debug
+    # node_api = NodeApi()
+    # if not node_api.verify_key():
+    # 	return JSONResponse(status_code=401, content=dict(message='not verified!', code=1))
+
+    # --- get params ---
+    params = await request.json()
+    username = params.get('username')
+    password = params.get('password')
+    # user = Global.mdb.get_one('User', {'username': username})
+    user = Global.mdb.get_one('UserInfo', {'username': username})
+    role_info = {1: '超级管理员'}
+    # if user:
+    #     role = Global.mdb.get_one_by_id('UserRole', user.get('role_id'))
+    #     role_name = role.get('role_name')
+    #     role_type = role.get('role_type')
+    # else:
+    #     role_name = ''
+    #     role_type = 0
+
+    # --- fail log---
+    if not user:
+        data = {
+            'username': username,
+            'is_login': 'Fail',
+            'role_type': user.get('role_type'),
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 2
+    elif not check_password_hash(user['password'], password):
+        data = {
+            'username': username,
+            'is_login': 'Fail',
+            'role_type': user.get('role_type'),
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 3
+    else:
+        data = {
+            'username': username,
+            'role_type': user.get('role_type'),
+            'is_login': 'Pass',
+            'login_at': methods.now_ts(),
+            'login_ip': request.client.host,
+        }
+        Global.mdb.add('UserLoginLog', data)
+        code = 0
+
+    # --- 登录失败 ---
+    if code:
+        return JSONResponse(status_code=401, content=dict(message='unauthorized access!', code=code))
+
+    # --- make token ---
+    data = {
+        'id': str(user['_id']),
+        'username': user['username'],
+        'password': user['password'],
+    }
+    token = serializer.dumps(data).decode('utf-8')
+    content = dict(
+        # message='authorization passed.',
+        # username=username,
+        # uid=str(user['_id']),
+        # rid=user.get('role_id'), code=0
+        code=0,
+        message='authorization passed.',
+        data={
+            'uid': str(user['_id']),
+            'username': username,
+            'role': role_info.get(user.get('role_type')),
+            'role_type': user.get('role_type'),
+            'token': token,
+        }
+    )
+    headers = {'authorization': token}
+    return JSONResponse(content=content, headers=headers)
+
+
+async def login_required(request: Request):
+    """
+    检查登录token
+    methods.debug_log('token.login_required.115', f"#code: {code}")
+    """
+
+    # --- check ---
+    # if request.method == 'POST':
+    #     sources = await request.json()
+    #     tag = sources.get('tag', 'v1')
+    #     code = int(sources.get('code'))
+    #     # methods.debug_log('token.login_required', f"m-107: code -> {code} | token -> {token}")
+    #     if not token and tag == 'v3' and code in [1102, 8201]:
+    #         methods.debug_log('token.login_required', f"m-103: code -> {code}")
+    #         superuser = Global.mdb.get_one('User', {'username': 'admin'})
+    #         superuser = Global.mdb.get_one('UserInfo', {'username': 'admin'})
+    #         return {
+    #             'uid': str(superuser.get('_id')),
+    #             'username': 'admin',
+    #             'password': 'admin',
+    #             'role_id': superuser.get('role_id'),
+    #             'skip_is': True,
+    #         }
+
+    # --- check --- todo 屏蔽token验证,正常情况下应放开
+    # if not token:
+    #     # raise HTTPException(status_code=401, detail='unauthorized access!')
+    #     raise HTTPException(status_code=401, headers=dict(message='unauthorized access!', code='4'))
+
+    # --- fill --- todo 屏蔽token验证,正常情况下应放开
+    # try:
+    #     data = serializer.loads(token)
+    #     user = Global.mdb.get_one_by_id('User', data['id'])
+    #     role_id = user.get('role_id')
+    #     # role_acl = Global.mdb.get_one_by_id('UserRole', role_id).get('role_acl')
+    #     return {
+    #         'uid': data['id'],
+    #         'username': data['username'],
+    #         'password': data['password'],
+    #         'role_id': role_id,
+    #     }
+    # except Exception as e:
+    #     # raise HTTPException(status_code=401, detail='unauthorized access!')
+    #     raise HTTPException(status_code=401, headers=dict(message='unauthorized access!', code='5'))
+
+    # todo 正常情况下应屏蔽
+    return {'skip_is': True}

+ 10 - 0
sri-server-bg01/main.py

@@ -0,0 +1,10 @@
+import uvicorn
+
+if __name__ == "__main__":
+    # --- check default data ---
+    from default_data_insert import default_data_insert
+
+    default_data_insert()
+
+    # uvicorn.run('app:app', host='0.0.0.0', port=9000)
+    uvicorn.run('app:app', host='0.0.0.0', port=9000, reload=True, debug=True)

+ 82 - 0
sri-server-bg01/main.spec

@@ -0,0 +1,82 @@
+# 用于pyinstaller打包用的配置文件
+# -*- mode: python ; coding: utf-8 -*-
+block_cipher = None
+a = Analysis(
+    [
+        'main.py',
+    ],
+    pathex = [
+
+        '../supplement-python',
+
+    ],
+    binaries = [
+
+        # find / -name "*_dl.cpython-36m-aarch64-linux-gnu.so*"
+        ('/usr/lib/aarch64-linux-gnu/libxcb.so.1', '.'),
+        ('/usr/lib/aarch64-linux-gnu/libdrm.so.2', '.'),
+
+    ],
+    datas = [
+
+    ],
+    hiddenimports = [
+
+        # --- for component ---
+        'actions',
+        'api',
+
+        # --- for supplement ---
+        'libraries.base_original',
+        'libraries.base_external.loop_by_aps',
+        'libraries.base_external.camera_by_cv2',
+        'libraries.base_external.data_by_numpy',
+        'clients.l4_ssh_by_paramiko',
+        'clients.db_mongo',
+        'clients.db_redis',
+        'clients.db_influx',
+        'apis.local.api',
+        'decorators',
+
+        # --- for middleware ---
+        'uvicorn.logging',
+        'uvicorn.loops',
+        'uvicorn.loops.auto',
+        'uvicorn.protocols',
+        'uvicorn.protocols.http',
+        'uvicorn.protocols.http.auto',
+        'uvicorn.protocols.websockets',
+        'uvicorn.protocols.websockets.auto',
+        'uvicorn.lifespan',
+        'uvicorn.lifespan.on',
+        'redis',
+        'pymongo',
+        'paramiko',
+        'apscheduler',
+
+    ],
+    hookspath = [],
+    runtime_hooks = [],
+    excludes = [],
+    win_no_prefer_redirects = False,
+    win_private_assemblies = False,
+    cipher = block_cipher,
+    noarchive = False,
+)
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+exe = EXE(
+    pyz,
+    a.scripts,
+    a.binaries,
+    a.zipfiles,
+    a.datas,
+    [],
+    name = 'main',  # <可执行文件名>
+    debug = False,
+    bootloader_ignore_signals = False,
+    strip = False,
+    upx = True,
+    upx_exclude = [],
+    runtime_tmpdir = None,
+    console = True,
+)

+ 8 - 0
sri-server-bg01/run-c.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+echo "Run-Commands(将py文件编译成so文件):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/build/component-interface \
+&& python3 cythonize.py build_ext --inplace \
+&& find /home/server/projects/taiwuict/cscec-8bur-vms/build -name "*.py" -type f | xargs rm -rf \
+&& echo "End."

+ 10 - 0
sri-server-bg01/run-exe-wrap.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+echo "Run-Commands(打包,打成可执行文件):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/component-interface \
+&& pyinstaller main.spec \
+    --onefile \
+    --distpath . \
+&& tar -cf main.tar main \
+&& ./main

+ 6 - 0
sri-server-bg01/run-exe.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+echo "Run-Commands(可执行文件方式运行):" \
+&& set -x \
+&& cd /home/server/projects/taiwuict/cscec-8bur-vms/component-interface \
+&& ./main

+ 7 - 0
sri-server-bg01/run.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+#echo "BEGIN:" \
+#&& python3 api.py
+
+echo "BEGIN(源码方式运行):" \
+&& python3 main.py

+ 127 - 0
sri-server-bg01/test/test-1000.py

@@ -0,0 +1,127 @@
+import requests
+
+# --- test 获取token ---
+url = 'http://58.34.94.177:29101/v1/token'
+data = {
+    'username': 'admin',  # 登录账户
+    'password': '123456',  # 登录密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'message': 'authorization passed.',
+#   'data': {
+#     'uid': '65dc4187e6e1bf491a4aad1f',
+#     'username': 'admin',
+#     'role': '超级管理员',
+#     'role_type': 1,
+#     'token': 'xxxxxxxx'
+#   }
+# }
+# """
+
+# --- test 1001 新增用户 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1001,  # 接口号
+#     'username': 'aabbcc',  # 登录账户(必须项)
+#     'password': 'aabbcc',  # 登录密码(可选项)(默认值:baosteel@2024)
+#     'role_type': 1,  # 角色类型(可选项)(1: 超级管理员 2: 普通管理员 3: 普通用户)
+#     'name': 'aabbcc',  # 姓名(可选项)
+#     'cid': '112233',  # 身份证号(可选项)
+#     'phone': '112233',  # 手机号(可选项)
+#     'sex': 0,  # 性别(可选项)(0:未设置 1:男 2:女)
+#     'department': 'aabbcc',  # 部门(可选项)
+#     'wid': 'aabbcc',  # 工号(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 1002 查询用户列表 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 1002,  # 接口号
+    'page': 1,  # 页码
+    'size': 3,  # 每页条数
+    'name': 'a',  # 模糊姓名(可选项)
+    'phone': '',  # 手机号(可选项)
+    'role_type': ['2'],  # 角色类型(可选项) 1 超级管理员 2 普通管理员 3 普通用户(默认值)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+"""
+{
+  'code': 0,
+  'data': [
+    {
+      'uuid': '65dbeb1c49fbe311a3a01d34',
+      'name': 'aabbcc',
+      'cid': '112233',
+      'phone': '112233',
+      'sex': 0,  # 性别 (0:未设置 1:男 2:女)
+      'department': 'aabbcc',
+      'wid': 'aabbcc',
+      'state': 0,  # 禁用状态 (0:激活 1:禁用)
+      'allow_at': 1708911388  # 注册审批时间时间戳
+    }
+  ]
+}
+"""
+
+# --- test 1003 删除指定用户 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1003,  # 接口号
+#     'uuid': '65db84b35e305885648ec062',  # 用户id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 1004 禁用指定用户 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1004,  # 接口号
+#     'uuid': '65decb7744b74ae33732d813',  # 用户id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 1005 启用指定用户 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1005,  # 接口号
+#     'uuid': '65decb7744b74ae33732d813',  # 用户id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 1006 获取指定用户详情 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1006,  # 接口号
+#     'uuid': '65dd775c44b74ae33732d7f7',  # 用户id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 1007 修改指定用户信息 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 1007,  # 接口号
+#     'uuid': '65dd775c44b74ae33732d7f7',  # 用户id(必须项)
+#     # 'username': 'aabbcc',  # 登录账户(必须项)
+#     # 'password': 'aabbcc',  # 登录密码(可选项)(默认值:baosteel@2024)
+#     # 'role_type': 3,  # 角色类型(可选项)(1: 超级管理员 2: 普通管理员 3: 普通用户)
+#     'name': 'aabbcc',  # 姓名(可选项)
+#     # 'cid': '112233',  # 身份证号(可选项)
+#     # 'phone': '112233',  # 手机号(可选项)
+#     # 'sex': 0,  # 性别(可选项)(0:未设置 1:男 2:女)
+#     # 'department': 'aabbcc',  # 部门(可选项)
+#     # 'wid': 'aabbcc',  # 工号(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())

+ 106 - 0
sri-server-bg01/test/test-2000.py

@@ -0,0 +1,106 @@
+import requests
+
+# --- test 获取token ---
+url = 'http://58.34.94.177:29101/v1/token'
+data = {
+    'username': 'admin',  # 用户名
+    'password': '123456',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+# print(code, token)
+
+# --- test 2001 新增车辆 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2001,  # 接口号
+#     'pid': 'AA998877',  # 车牌号(必须项)
+#     'type': '112233',  # 车型号(可选项)
+#     'host_address': 'xxx.xxx.xxx.xxx',  # 工控机地址(可选项)
+#     'rtk_address': 'xxx.xxx.xxx.xxx',  # rtk地址(可选项)
+#     'cpe_address': 'xxx.xxx.xxx.xxx',  # cpe地址(可选项)
+#     'release_at': '2022-12-12',  # 出厂日期(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 2002 查询车辆列表 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2002,  # 接口号
+#     'page': 1,  # 页码
+#     'size': 3,  # 每页条数
+#     'pid': 'AA',  # 模糊查询,车牌号(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': [
+#     {
+#       'uuid': '65dbe96949fbe311a3a01d30',
+#       'pid': 'AA112233',
+#       'type': '112233',
+#       'host_address': 'xxx.xxx.xxx.xxx',
+#       'rtk_address': 'xxx.xxx.xxx.xxx',
+#       'cpe_address': 'xxx.xxx.xxx.xxx',
+#       'release_at': '2022-12-12',
+#       'state': 1,  # 当前状态 (1:离线 2:在线空闲 3: 现场驾驶中 4: 远程驾驶中)
+#       'permit_state': 0  # 遥操状态 (0:允许 1:禁用)
+#     }
+#   ]
+# }
+# """
+
+# --- test 2003 修改指定车辆信息 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2003,  # 接口号
+#     'uuid': '65dbe96949fbe311a3a01d30',  # 车辆id(必须项)
+#     'pid': 'AA112233',  # 车牌号(可选项)
+#     'type': '三一/SMHW48',  # 车型号(可选项)
+#     'host_address': 'xxx.xxx.xxx.xxx',  # 工控机地址(可选项)
+#     'rtk_address': 'xxx.xxx.xxx.xxx',  # rtk地址(可选项)
+#     'cpe_address': 'xxx.xxx.xxx.xxx',  # cpe地址(可选项)
+#     'release_at': '2022-12-12',  # 出厂日期(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 2004 禁止指定车辆远程操作 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2004,  # 接口号
+#     'uuid': '65dbe96949fbe311a3a01d30',  # 车辆id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 2005 允许指定车辆远程操作 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2005,  # 接口号
+#     'uuid': '65dbe96949fbe311a3a01d30',  # 车辆id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 2006 删除指定作业车辆 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 2006,  # 接口号
+#     'uuid': '65dd95e244b74ae33732d810',  # 车辆id(必须项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+
+# --- test 2007 获取指定作业车辆详情 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 2007,  # 接口号
+    'uuid': '65de9fa044b74ae33732d811',  # 车辆id(必须项)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())

+ 49 - 0
sri-server-bg01/test/test-3000.py

@@ -0,0 +1,49 @@
+import requests
+
+# --- test 获取token ---
+url = 'http://58.34.94.177:29101/v1/token'
+data = {
+    'username': 'admin',  # 用户名
+    'password': '123456',  # 密码
+}
+response = requests.post(url=url, json=data)
+code = response.json().get('code')
+token = response.headers.get('authorization')
+# print(code, token)
+
+# --- test 3001 查询驾驶人员操作记录列表 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# data = {
+#     'code': 3001,  # 接口号
+#     'page': 1,  # 页码
+#     'size': 3,  # 每页条数
+#     'pid': 'AA',  # 模糊查询,车牌号(可选项)
+#     'driver_name': 'AABBXX',  # 模糊查询,驾驶员(可选项)
+# }
+# response = requests.post(url=url, json=data, headers={'authorization': token})
+# print(response.json())
+# """
+# {
+#   'code': 0,
+#   'data': [
+#     {
+#       'uuid': '65dbe96949fbe311a3a01d30',
+#       'pid': 'AA112233',
+#       'start_time_at': 1708917227,
+#       'end_time_at': 1708946027,
+#       'driver_name': '张三',
+#       'cockpit_name': '1号舱'
+#     }
+#   ],
+#   'total': 1,
+#   'page': 1,
+#   'size': 3
+# }
+# """
+
+# --- test 3002 下载指定驾驶人员操作日志 ---
+# url = 'http://58.34.94.177:29101/v6/api'
+# params = {'code': 3002}
+# response = requests.get(url=url, params=params, headers={'authorization': token})
+# print(response.text)
+# requests.get(url='http://58.34.94.177:29101/v6/api?code=3002').text

+ 39 - 0
sri-server-bg01/test/接口文档(第2批,共5个,已部署5个).txt

@@ -0,0 +1,39 @@
+# --- test 2006 删除指定车辆 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 2006,  # 接口号
+    'uuid': '65dbe96949fbe311a3a01d30',  # 车辆id(必须项)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+
+# --- test 2007 获取指定作业车辆详情 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 2007,  # 接口号
+    'uuid': '65de9fa044b74ae33732d811',  # 车辆id(必须项)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+
+# --- test 1005 启用指定用户 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 1005,  # 接口号
+    'uuid': '65decb7744b74ae33732d813',  # 用户id(必须项)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+
+# --- test 1006 获取指定用户详情 ---
+url = 'http://58.34.94.177:29101/v6/api'
+data = {
+    'code': 1006,  # 接口号
+    'uuid': '65decb7744b74ae33732d813',  # 用户id(必须项)
+}
+response = requests.post(url=url, json=data, headers={'authorization': token})
+print(response.json())
+
+# --- test 3002 下载指定驾驶人员操作日志 ---
+url = 'http://58.34.94.177:29101/v6/api?code=3002'
+response = requests.get(url=url)

+ 39 - 0
sri-server-bg02/Dockerfile

@@ -0,0 +1,39 @@
+FROM ubuntu:20.04
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN echo "Install Python v3.8:" \
+    && apt-get update \
+    && apt-get install -y  \
+        python3-dev \
+        python3-pip \
+        python3-setuptools \
+        python3-wheel \
+    && pip3 config set global.index-url https://mirror.baidu.com/pypi/simple \
+    && pip3 config set global.extra-index-url https://pypi.tuna.tsinghua.edu.cn/simple \
+    && pip3 install --upgrade --quiet pip setuptools \
+    && python3 --version
+
+# --- install requirements ---
+RUN echo "Install Python Requirements:" \
+    && pip3 install --default-timeout=1800 --no-cache-dir \
+        # --- for base --- \
+        cython==3.0.0a9 \
+        pyinstaller==4.10 \
+        # --- for libraries --- \
+        pycrypto==2.6.1 \
+        paramiko==2.7.2 \
+        apscheduler==3.7.0 \
+        # --- for client --- \
+        requests==2.25.1 \
+        redis==3.5.3 \
+        pymongo==3.11.2 \
+        influxdb==5.3.1 \
+        pymysql==0.9.3 \
+        peewee==3.17.0 \
+        SQLAlchemy==1.4.30 \
+        paho-mqtt==1.6.1 \
+    && echo "End."
+
+RUN echo "Debug Tools:" \
+    && apt-get update \
+    && apt-get install -y inetutils-ping iproute2 net-tools wget unzip git bash

+ 8 - 0
sri-server-bg02/README-usage.bash

@@ -0,0 +1,8 @@
+## NOTE
+
+echo "执行:启动" \
+&& project_path="/home/server/repositories/repositories/gitee.com/casperz.py-project/project-responder-bg" \
+&& cd ${project_path} \
+&& sudo docker-compose --file compose.yml down \
+&& sudo docker-compose --file compose.yml up --detach --build \
+&& sudo docker-compose --file compose.yml logs --follow

+ 67 - 0
sri-server-bg02/api/Command.py

@@ -0,0 +1,67 @@
+from hub import methods, Global
+
+
+class Command(object):
+
+    @classmethod
+    def cmd001(cls, **params):
+        """
+        url:
+            http://192.168.30.13:8892/api?module=Command&method=cmd001&version_id=d986be1
+            http://192.168.1.33:8892/api?module=Command&method=cmd001&version_id=d986be1
+        cmd:
+            echo "版本:2021年4月13日 13:58:11" \
+            && version_id="2d796e9" \
+            && image_name="39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1" \
+            && script_dir="/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1" \
+            && sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash ${script_dir}/deployer-update_by_commit.sh ${version_id} \
+            && sudo bash ${script_dir}/07.restart_service.sh
+        """
+        version_id = params.get('version_id')
+        ssh = SSHClient('172.18.0.1', 22, 'server', 'server')
+        command = str()
+        command += f"version_id=\"{version_id}\""
+        command += f" && "
+        command += f"image_name=\"39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1\""
+        command += f" && "
+        command += f"script_dir=\"/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1\""
+        command += f" && "
+        command += r"sudo docker login -u admin -p admin http://39.99.150.29:9999"
+        command += f" && "
+        command += r"sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash "
+        command += r"${script_dir}/deployer-update_by_commit.sh ${version_id}"
+        command += f" && "
+        command += r"sudo bash ${script_dir}/07.restart_service.sh"
+        command += f" && "
+        command += r"sudo chmod -R 777 /home/server/projects/taiwuict/taiwuict-aibox"
+        methods.debug_log(f"Command.cmd001", f"1: {command}")
+        ssh.run_command(command)
+        return dict(code=0)
+
+    @classmethod
+    def cmd002(cls, **params):
+        """
+        url:
+            http://192.168.30.14:8892/api?module=Command&method=cmd002
+        cmd:
+            echo "版本:2021年4月13日 13:58:11" \
+            && image_name="39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1" \
+            && script_dir="/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1" \
+            && sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash ${script_dir}/deployer-update_pth.sh \
+            && sudo docker restart node_yolo
+        """
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        command = str()
+        command += f"image_name=\"39.99.150.29:9999/aibox-component-dashboard-packer:1.0.1\""
+        command += f" && "
+        command += f"script_dir=\"/home/server/projects/taiwuict/taiwuict-aibox/developer-operation/l1\""
+        command += f" && "
+        command += r"sudo docker login -u admin -p admin http://39.99.150.29:9999"
+        command += f" && "
+        command += r"sudo docker run --interactive --volume /home:/home ${image_name} /bin/bash "
+        command += r"${script_dir}/deployer-update_pth.sh"
+        command += f" && "
+        command += r"sudo docker restart node_yolo"
+        methods.debug_log(f"Command.cmd002", f"1: {command}")
+        ssh.run_command(command)
+        return dict(code=0)

+ 58 - 0
sri-server-bg02/api/Key.py

@@ -0,0 +1,58 @@
+from hub import methods, Global
+
+
+class Key(object):
+
+    @classmethod
+    def create_key(cls, **params):
+        """
+        生成key
+        """
+        # --- check ---
+        key_path = '/home/server/key'
+        if methods.is_file(key_path):
+            methods.remove_file(key_path)
+
+        # --- get serial ---
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        serial = ssh.run_command("cat /sys/firmware/devicetree/base/serial-number").strip('\x00')
+
+        # --- generate salt ---
+        salt = methods.now_ts()
+        salt = methods.reverse(str(salt))
+        salt = 0
+
+        # --- generate key ---
+        keys = methods.text_to_b64(f"{serial}|{salt}")
+
+        # --- storage env_key ---
+        methods.run_command(f"echo \"{keys}\" > {key_path}", callback=True)
+        return dict(code=0, data=keys)
+
+    @classmethod
+    def verify_key(cls, **params):
+        """
+        验证key
+        """
+        # --- check ---
+        if not methods.is_file('/home/server/key'):
+            return dict(code=1, is_verified=False)
+
+        # --- get key ---
+        keys = methods.read_text('/home/server/key')
+        keys = methods.b64_to_text(keys)
+
+        # --- check again ---
+        if not keys:
+            return dict(code=2, is_verified=False)
+        if len(keys.split('|')) != 2:
+            return dict(code=3, is_verified=False)
+        serial_1, _ = keys.split('|')
+
+        # --- check again ---
+        ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+        serial_2 = ssh.run_command("cat /sys/firmware/devicetree/base/serial-number").strip('\x00')
+        if serial_1 != serial_2:
+            return dict(code=4, is_verified=False)
+
+        return dict(code=0, is_verified=True)

+ 515 - 0
sri-server-bg02/api/TEST.py

@@ -0,0 +1,515 @@
+from hub import methods, Global, numpy_method, camera_handle
+from factories.line_manage import LineManage
+
+import base64
+import cv2
+import os
+
+
+class TEST(object):
+
+    @classmethod
+    def test002(cls, **params):
+        """
+        """
+        try:
+
+            # --- define ---
+            run_at = methods.now_ts()
+            condition = {
+                'face_name': {'$ne': None},
+            }
+            total = Global.mdb.get_count('Face', condition)
+            items = Global.mdb.filter('Face', condition)
+            count = 0
+
+            for item in items:
+                # --- log ---
+                count += 1
+                methods.debug_log('TEST.test002', f"m-24: {count}/{total}")
+
+                # --- check ---
+                face_image_path = item.get('face_feature_info_list')[0].get('face_image_path')
+                face_image_path = f"{face_image_path[:-4]}-fake.jpg"
+                if not os.path.isfile(face_image_path):
+                    continue
+
+                # --- 提取特征 ---
+                image_array = cv2.imread(face_image_path)
+                result = Global.scrfd_agent.inference_with_image_array(image_array)
+                face_image = result[0].get('align_face')
+                face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+                # --- check null ---
+                if face_features is None:
+                    methods.debug_log('TEST.test002', f"m-46: face_features is None! | {face_image_path}")
+                    continue
+
+                # --- update ---
+                face_uuid = str(item.get('_id'))
+                face_feature_info_list = item.get('face_feature_info_list')
+                face_feature_info_list.append(
+                    {
+                        'face_object_confidence': 1.0,
+                        'face_features': methods.pickle_dumps(face_features),
+                        'face_image_path': face_image_path,
+                        'detector_name': 'scrfd',
+                        'mask_on_face_is': False,
+                    }
+                )
+                Global.mdb.update_one_by_id('Face', face_uuid, {'face_feature_info_list': face_feature_info_list})
+                methods.debug_log('TEST.test002', f"m-62: face_image_path -> {face_image_path} | {face_uuid}")
+
+            # --- log ---
+            methods.debug_log('TEST.test002', f"m-32: time use {round(methods.now_ts() - run_at, 2)}s, "
+                                              f"update count is {count}")
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test002', f"m-84: exception | {exception}")
+            methods.debug_log('TEST.test002', f"m-84: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test001(cls, **params):
+        """批量导入人脸特征数据"""
+        try:
+            # --- define ---
+            api = Global.get_yzw_client()
+            db1 = Global.get_mariadb_client()
+            db2 = Global.get_mongodb_client()
+            run_at = methods.now_ts()
+
+            # --- get mariadb.worker_info ---
+            count = 0
+            items = db1.get_all('worker_info')
+            for item in items:
+
+                # --- debug log ---
+                count += 1
+                if count % 1000 == 0:
+                    methods.debug_log('TEST.test001', f"count: {count}")
+
+                # --- check null ---
+                if not item.image_path:
+                    continue
+
+                # --- set tags if not exists ---
+                data = db2.get_one(Global.dataset_name, {'prc_id': item.worker_id})
+                project_name_tags = data.get('project_name_tags', list())
+                worker_type_tags = data.get('worker_type_tags', list())
+                if item.project_name and item.project_name not in project_name_tags:
+                    project_name_tags.append(item.project_name)
+                if item.worker_type and item.worker_type not in worker_type_tags:
+                    worker_type_tags.append(item.worker_type)
+
+                # --- set face if not exists ---
+                face_features = data.get('face_features')
+                if not face_features:
+
+                    # --- check null ---
+                    image_bytes = api.get_image(item.image_path)
+                    if not image_bytes:
+                        # methods.debug_log('TEST.test001', f"d1: {item.worker_id}")
+                        continue
+
+                    # --- get face features ---
+                    face_features = DetectFaceEngine.get_face_features_by_image_bytes(image_bytes)
+                    # methods.debug_log('TEST.test001', f"d1: {face_features}")
+
+                # --- check null ---
+                if not face_features:
+                    # methods.debug_log('TEST.test001', f"d1: {item.worker_id}")
+                    continue
+
+                # --- set mongodb.VisitorInfo ---
+                unique_dict = {
+                    'prc_id': item.worker_id,
+                }
+                update_dict = {
+                    'face_name': item.worker_name,
+                    'face_path': f"https://lwres.yzw.cn/{item.image_path}",
+                    'face_features': methods.pickle_dumps(face_features),
+                    'project_name_tags': project_name_tags,
+                    'worker_type_tags': worker_type_tags,
+                }
+                db2.update_one(Global.dataset_name, unique_dict, update_dict)
+                # break
+
+            methods.debug_log('TEST.test001', f"t1: {methods.now_ts() - run_at}s")
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test001', f"m-84: exception | {exception}")
+            methods.debug_log('TEST.test001', f"m-84: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test004(cls, **params):
+        """更新 mongo.VisitorTags 表数据"""
+        run_at = methods.now_ts()
+        try:
+
+            # --- update mongodb.VisitorInfo ---
+            mdb = Global.get_mongodb_client()
+            for item in mdb.get_all(Global.dataset_name):
+
+                # --- update ProjectName ---
+                project_name_tags = item.get('project_name_tags', [])
+                for project_name in project_name_tags:
+                    unique_dict = {
+                        'tag_type': 'ProjectName',
+                        'tag_name': project_name,
+                    }
+                    update_dict = {}
+                    mdb.update_one('VisitorTags', unique_dict, update_dict)
+
+                # --- update ProjectName ---
+                worker_type_tags = item.get('worker_type_tags', [])
+                for worker_type in worker_type_tags:
+                    unique_dict = {
+                        'tag_type': 'WorkerType',
+                        'tag_name': worker_type,
+                    }
+                    update_dict = {}
+                    mdb.update_one('VisitorTags', unique_dict, update_dict)
+
+            # --- todo 遍历 VisitorTags 表 然后更新每条数据的 face_amout 字段 统计每个标签所属的人数
+
+            methods.debug_log('TEST.test004', f"m1: {methods.now_ts() - run_at}s")
+            return dict(code=0)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test004', f"m-124: exception | {exception}")
+            methods.debug_log('TEST.test004', f"m-124: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test006(cls, **params):
+        """
+        crop_img=np.array(faces[i]) # 每张图中的单个人脸图像
+        cv2.imwrite('crop.jpg',crop_img)
+        """
+        try:
+            # --- define ---
+            mdb = Global.get_mongodb_client()
+
+            item = mdb.get_one_by_id('Face', '622fffd65bda389a51c96c24')
+            face_features_1 = methods.pickle_loads(item.get('face_features'))
+
+            item = mdb.get_one_by_id('Face', '622fff612ae2021a236e2c90')
+            face_features_2 = methods.pickle_loads(item.get('face_features'))
+
+            dist = Global.arcface_agent.compare_faces_by_normalization(face_features_1, face_features_2)
+            methods.debug_log('TEST.test006', f"m-196: dist: {dist}")
+            return dict(code=0, details=f"End.", dist=dist)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test006', f"m-124: exception | {exception}")
+            methods.debug_log('TEST.test006', f"m-124: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test007(cls, **params):
+        """url测试websocket在线信息"""
+        try:
+            line_total = LineManage.get_line_total()
+            line_state = LineManage.get_line_state()
+            return dict(code=0, line_total=line_total, line_state=line_state)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test007', f"m-161: exception | {exception}")
+            methods.debug_log('TEST.test007', f"m-161: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test008(cls, **params):
+        """"""
+        try:
+            # --- define ---
+            api = Global.cscec_agent()
+
+            # --- get face b64 ---
+            file_path = f"/home/server/resources/vms-files/2021-0826-160627-600346.pickle"
+            image = methods.load_pickle_file(file_path)  # 解压
+            frame = numpy_method.to_array(image)  # list to numpy array
+            _, image = cv2.imencode('.jpg', frame)
+            base64_data = base64.b64encode(image)  # byte to b64 byte
+            s = base64_data.decode()  # byte to str
+            face_image_b64 = f'data:image/jpeg;base64,{s}'
+
+            # --- test push_face ---
+            methods.debug_log('TEST.test008', f"m-236: {type(methods.now_string())}")
+            api.push_face(face_image_b64=face_image_b64, now_at=methods.now_string())
+
+            # --- test push_face_log ---
+            api.push_face_log()
+
+            # --- test ---
+            api.pull_alarm_list()
+            api.pull_alarm_group_list()
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+            methods.debug_log('TEST.test008', f"m-196: exception | {exception}")
+            methods.debug_log('TEST.test008', f"m-196: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test009(cls, **params):
+        """"""
+        try:
+            file_path = '/home/server/resources/vms-files/2021-0822-161705-929476.pickle'
+            image = methods.load_pickle_file(file_path)  # 解压
+            frame = numpy_method.to_array(image)  # list to numpy array
+            _, image = cv2.imencode('.jpg', frame)
+            base64_data = base64.b64encode(image)  # byte to b64 byte
+            s = base64_data.decode()  # byte to str
+            face_image_b64 = f'data:image/jpeg;base64,{s}'
+            return dict(code=0, face_image_b64=face_image_b64)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test009', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test009', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test011(cls, **params):
+        """
+        下载语音文件
+        http://192.168.30.13:8800/api?module=TEST&method=test011&mp3_name=2021-08-17-14-38-49-223380.mp3
+        http://192.168.30.13:8800/api?module=TEST&method=test011&mp3_name=2021-08-17-14-59-15-665168.mp3
+        """
+        try:
+            # --- check ---
+            mp3_name = params.get('mp3_name')
+            if not mp3_name:
+                return dict(code=1, details=f"something is wrong.")
+
+            # --- check ---
+            file_path = f"/home/server/resources/mp3-files/{mp3_name}"
+            if not methods.is_file(file_path):
+                return dict(code=2, details=f"something is wrong.")
+
+            return methods.read_bytes(file_path)
+
+        except Exception as exception:
+
+            methods.debug_log('Task.test011', f"m-239: exception | {exception}")
+            methods.debug_log('Task.test011', f"m-239: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test012(cls, **params):
+        """
+        录入人脸图片
+        """
+        try:
+            # --- 提取特征 ---
+            # file_name = 'IMG_20220315_101522.jpg'
+            # file_name = 'IMG_20220315_101649.jpg'
+            # file_name = '2022-0324-154547-015729.jpg'
+            # file_name = 'bing.jpg'
+            file_name = 'qiang.jpg'
+            image_array = cv2.imread(f'/home/server/resources/TestData/2022/0315/{file_name}')
+
+            # --- 剪裁人脸 ---
+            # inference_result = Global.scrfd_agent.inference(image_array)
+            # image_array = inference_result[0].get('face_image')
+
+            # --- 特征提取 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(image_array)
+
+            # --- save file ---
+            save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+            face_file_path = f"/home/server/resources/vms-files/{save_at}.jpg"
+            cv2.imwrite(face_file_path, image_array)
+
+            # --- save mongodb ---
+            """
+            Face: 陌生人脸表
+            Face.face_features: 人脸特征
+            Face.face_image_path: 人脸图像文件路径
+            Face.create_at: 录入时间
+            Face.prc_id: 身份证号
+            Face.face_image_url: 人脸地址
+            Face.face_name: 人员姓名
+            """
+            unique_dict = {
+                'prc_id': file_name,
+            }
+            update_dict = {
+                'face_features': methods.pickle_dumps(face_features),
+                'face_image_path': face_file_path,
+                'create_at': methods.now_ts(),
+                'face_image_url': None,
+                # 'face_name': 'zhang',
+                # 'face_name': 'bing',
+                'face_name': '陆强',
+            }
+            Global.mdb.update_one('Face', unique_dict, update_dict)
+
+            return dict(code=0, details=f"end.")
+
+        except Exception as exception:
+
+            methods.debug_log('TEST.test012', f"m-297: exception | {exception}")
+            methods.debug_log('TEST.test012', f"m-297: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test013(cls, **params):
+        """对比人脸图像的相似度"""
+        try:
+            # --- test 1 ---
+
+            # face_path = '/home/server/resources/vms-files/2022-0507-152213-741783.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_0 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0513/lq1.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0513/lq2.jpg'
+            # result = Global.scrfd_agent.inference(face_path)
+            # face_image = result[0].get('face_image')
+            # face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_1)
+            # dist_2 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_2)
+
+            # --- test 2 ---
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/001.jpg'  # 0.7584765553474426
+            # face_path = '/home/server/resources/TestData/2022/0531-1/002.jpg'  # 0.8457706868648529
+            face_path = '/home/server/resources/TestData/2022/0531-1/004.jpg'  # 0.8554660677909851 0.8809504210948944
+            # face_path = '/home/server/resources/TestData/2022/0531-1/009.jpg'  # 0.7084800899028778
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/101.jpg'  # 0.7629551887512207
+            # face_path = '/home/server/resources/TestData/2022/0531-1/102.jpg'  # 0.8441838622093201
+            face_path = '/home/server/resources/TestData/2022/0531-1/104.jpg'  # 0.8168102502822876 0.8709447979927063
+            # face_path = '/home/server/resources/TestData/2022/0531-1/109.jpg'  # 0.6636235117912292
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # face_path = '/home/server/resources/TestData/2022/0531-1/201.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0531-1/202.jpg'
+            face_path = '/home/server/resources/TestData/2022/0531-1/204.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0531-1/209.jpg'
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # face_image = result[0].get('raw_face')
+            face_image = result[0].get('align_face')
+            face_features_3 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_1, face_features_3)
+            dist_2 = Global.arcface_agent.compare_faces_by_normalization(face_features_2, face_features_3)
+
+            return dict(code=0, dist_1=dist_1, dist_2=dist_2)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test013', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test013', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test014(cls, **params):
+        """
+        对比人脸图像的相似度
+        face-uuid
+        62a008b32bb1ca6081d36b1a
+        base
+        629eb0fb1ef13cc2688962a4
+        """
+        try:
+
+            # item = Global.mdb.get_one_by_id('Face', '627621d4013c78548cee0020')  # 于佑飞
+            # item = Global.mdb.get_one_by_id('Face', '62761dc5013c78548cedfff4')  # 王彦杰
+            item = Global.mdb.get_one_by_id('Face', '629eb0fb1ef13cc2688962a4')  # test
+            face_features_0 = methods.pickle_loads(item.get('face_feature_info_list')[0].get('face_features'))
+
+            item = Global.mdb.get_one_by_id('Face', '62a008b32bb1ca6081d36b1a')  # test
+            face_features_1 = methods.pickle_loads(item.get('face_feature_info_list')[0].get('face_features'))
+
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640682.jpg'
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640715.jpg'
+            # image_array = cv2.imread(face_path)
+            # result = Global.scrfd_agent.inference_with_image_array(image_array)
+            # # face_image = result[0].get('raw_face')
+            # face_image = result[0].get('align_face')
+            # face_features_1 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            dist_1 = Global.arcface_agent.compare_faces_by_normalization(face_features_0, face_features_1)
+            return dict(code=0, dist_1=dist_1)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test014', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test014', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def test015(cls, **params):
+        """
+        检索相似人脸
+        """
+        try:
+
+            # face_path = '/home/server/resources/TestData/2022/0527/1653640682.jpg'
+            face_path = '/home/server/resources/TestData/2022/0629/20220629090605.jpg'
+            image_array = cv2.imread(face_path)
+            result = Global.scrfd_agent.inference_with_image_array(image_array)
+            face_image = result[0].get('align_face')
+            face_features_0 = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # --- fill face_dict ---
+            face_dict = dict()
+            condition = {
+                'face_name': {'$ne': None},  # 只获取人为标注的底库
+            }
+            items = Global.mdb.filter('Face', condition)
+            for index, item in enumerate(items):
+
+                # --- define ---
+                face_uuid = str(item.get('_id'))
+                face_dict[face_uuid] = list()
+
+                # --- check ---
+                face_feature_info_list = item.get('face_feature_info_list')
+                if not face_feature_info_list:
+                    continue
+
+                # --- fill ---
+                for face_feature_info in face_feature_info_list:
+                    face_features = face_feature_info.get('face_features')
+                    if not face_features:
+                        continue
+                    if methods.pickle_loads(face_features) is None:
+                        continue
+                    d1 = methods.pickle_loads(face_features), face_feature_info.get('face_image_path')
+                    face_dict[face_uuid].append(d1)
+
+            run_at = methods.now_ts(unit='ms')
+            face_uuid, face_dist, base_face_image_path = Global.arcface_agent.search_face_v2(face_features_0, face_dict)
+            use_time = round((methods.now_ts(unit='ms') - run_at), 2)
+            methods.debug_log('TEST.test015', f"m-439: use time {use_time}ms")
+
+            return dict(code=0, face_uuid=face_uuid, face_dist=face_dist, base_face_image_path=base_face_image_path,
+                        use_time=use_time)
+
+        except Exception as exception:
+            methods.debug_log('TEST.test015', f"m-214: exception | {exception}")
+            methods.debug_log('TEST.test015', f"m-214: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")

+ 222 - 0
sri-server-bg02/api/Task.py

@@ -0,0 +1,222 @@
+from hub import methods, Global
+
+import cv2
+import base64
+
+
+class Task(object):
+
+    @classmethod
+    def task002(cls, **params):
+        """获取特征值接口"""
+        run_at = methods.now_ts()
+        try:
+
+            # --- get face_features ---
+            call_id = params.get('call_id')
+            save_is = params.get('save_is')
+            rdb = Global.get_redis_client()
+            image_bytes = rdb.get_one(key=call_id)
+
+            # --- 人脸裁剪逻辑 ---
+            # methods.debug_log('Task.task002', f"m-24: {type(image_bytes)}")
+            image_array = Global.scrfd_agent.image_bytes_to_image_array(image_bytes)
+            # methods.debug_log('Task.task002', f"m-24: {type(image_array)}")
+            # inference_result = Global.scrfd_agent.inference(image_array)
+            inference_result = Global.scrfd_agent.inference_with_image_array(image_array)
+
+            # --- save file ---
+            if save_is:
+                save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+                frame = Global.arcface_agent.image_bytes_to_image_array(image_bytes)
+                face_file_path = f"/home/server/resources/vms-files/{save_at}-raw.jpg"
+                cv2.imwrite(face_file_path, frame)
+            else:
+                face_file_path = ''
+
+            # --- check ---
+            if not inference_result:
+                return dict(code=1, details=f"something is wrong.")
+
+            # --- 歪脸矫正 ---
+            face_image = inference_result[0].get('align_face')
+            probability = 0.0
+
+            # --- 提取人脸特征 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+
+            # --- check ---
+            if face_features is None:
+                return dict(code=2, details=f"something is wrong.")
+
+            # --- debug to close --- todo 存在歪脸矫正后,图像图像变模糊的问题
+            # save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+            # face_file_path_x = f"/home/server/resources/vms-files/{save_at}-align.jpg"
+            # cv2.imwrite(face_file_path_x, face_image)
+
+            # --- 增加戴口罩人脸底库特征与图片 ---
+            masked_face_image = Global.face_to_mask_face.add_mask_one(face_image)
+            if masked_face_image is not None:
+                face_features_2 = Global.arcface_agent.get_face_features_normalization_by_image_array(masked_face_image)
+            else:
+                face_features_2 = face_features
+                masked_face_image = face_image
+
+            # --- save file ---
+            if save_is:
+                save_at = methods.now_string('%Y-%m%d-%H%M%S-%f')
+                face_file_path_2 = f"/home/server/resources/vms-files/{save_at}-masked.jpg"
+                cv2.imwrite(face_file_path_2, masked_face_image)
+            else:
+                face_file_path_2 = ''
+
+            # --- update call_id value ---
+            rdb.set_one(key=call_id, data=(face_features, probability, face_file_path,
+                                           face_features_2, probability, face_file_path_2), expire_time=600)
+            return dict(code=0, details=f"congratulations. | use time {methods.now_ts() - run_at}s")
+
+        except Exception as exception:
+
+            methods.debug_log('Task.task002', f"m-76: exception | {exception}")
+            methods.debug_log('Task.task002', f"m-76: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")
+
+    @classmethod
+    def task003(cls, **params):
+        """
+        支持8301接口
+        """
+        run_at = methods.now_ts()
+        try:
+            # --- get data ---
+            call_id = params.get('call_id')
+            rdb = Global.get_redis_client()
+            input_data = rdb.get_one(key=call_id)
+
+            # --- 人脸裁剪逻辑 ---
+            image_array = Global.scrfd_agent.image_bytes_to_image_array(input_data.get('image_bytes'))
+            inference_result = Global.scrfd_agent.inference_with_image_array(image_array)
+
+            # --- 歪脸矫正 ---
+            face_image = inference_result[0].get('align_face')
+
+            # --- 提取人脸特征 ---
+            face_features = Global.arcface_agent.get_face_features_normalization_by_image_array(face_image)
+            # methods.debug_log('Task.task003', f"m-105: face_features | {face_features}")
+
+            # --- fill face_type_name_dict ---
+            """
+            face_type_name_dict = {<type_uuid>: <name>}
+            """
+            face_type_name_dict = dict()
+            for item in Global.mdb.get_all('FaceType'):
+                uuid = str(item.get('_id'))
+                face_type_name_dict[uuid] = item.get('name')
+
+            # --- fill d5 ---
+            d5 = dict()  # {<face_uuid>: {info}}
+            for item in Global.mdb.get_all('Face'):
+                face_uuid = str(item.get('_id'))
+                d5[face_uuid] = item.copy()
+
+            # --- fill d2 ---
+            """
+            FaceLog: 陌生人访问日志表
+            FaceLog.face_image_path: 抓拍人脸图像路径
+            FaceLog.face_probability: 人脸概率
+            FaceLog.base_face_uuid: 底库人脸id
+            FaceLog.base_face_image_path: 底库人脸图片路径
+            FaceLog.face_similarity: 对比相似度
+            FaceLog.count: 条目计数
+            """
+            d2 = list()
+            start_ts = methods.string_to_ts(f"{input_data.get('start_date_at')}", pattern='%Y-%m-%d %H:%M:%S')
+            end_ts = methods.string_to_ts(f"{input_data.get('end_date_at')}", pattern='%Y-%m-%d %H:%M:%S') + 1
+            items = list(Global.xdb.filter_by_time_range('FaceLog', start_ts, end_ts))
+            for item in items:
+
+                # --- check ---
+                base_face_uuid = item.get('base_face_uuid')
+                if base_face_uuid not in d5:
+                    continue
+
+                # --- check ---
+                # face = Global.mdb.get_one_by_id('Face', base_face_uuid)
+                face = d5.get(base_face_uuid)
+                face_features_1 = methods.pickle_loads(face.get('face_feature_info_list')[0].get('face_features'))
+                if face_features_1 is None:
+                    continue
+
+                # --- check ---
+                dist = Global.arcface_agent.compare_faces_by_normalization(face_features, face_features_1)
+                if dist < input_data.get('face_similarity'):
+                    continue
+
+                # --- fill ---
+                d4 = item, face, dist
+                d2.append(d4)
+
+            # --- fill d3 ---
+            d3 = list()
+            page = input_data.get('page')
+            size = input_data.get('size')
+            for item, face, dist in d2[(page - 1) * size: page * size]:
+
+                # --- fill d1 --- todo 返回数据 -> 抓拍照片、检索照片、相似度
+                d1 = {
+                    'face_uuid': item.get('base_face_uuid'),
+                    'face_name': face.get('face_name'),
+                    'record_at': methods.string_to_ts(item.get('time'), pattern='%Y-%m-%dT%H:%M:%S.%fZ'),
+                    'snap_face_image_b64': str(),  # 抓拍照片
+                    'base_face_image_b64': str(),  # 对比照片
+                    'face_type_name_list': list(),  # 类别名称
+                    'face_similarity': dist,  # 相似度
+                }
+                # methods.debug_log('actions.action_8201', f"m-86: d1 is {d1}")
+
+                # --- fill snap_face_image_b64 ---
+                file_path = item.get('face_image_path')
+                if file_path and methods.is_file(file_path):
+                    frame = cv2.imread(file_path)
+                    if frame is not None:
+                        _, image = cv2.imencode('.jpg', frame)
+                        base64_data = base64.b64encode(image)  # byte to b64 byte
+                        s = base64_data.decode()  # byte to str
+                        d1['snap_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill base_face_image_b64 ---
+                # file_path = item.get('base_face_image_path')
+                # if file_path and methods.is_file(file_path):
+                #     frame = cv2.imread(file_path)
+                #     if frame is not None:
+                #         _, image = cv2.imencode('.jpg', frame)
+                #         base64_data = base64.b64encode(image)  # byte to b64 byte
+                #         s = base64_data.decode()  # byte to str
+                #         d1['base_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill base_face_image_b64 ---
+                frame = Global.arcface_agent.image_bytes_to_image_array(input_data.get('image_bytes'))
+                if frame is not None:
+                    _, image = cv2.imencode('.jpg', frame)
+                    base64_data = base64.b64encode(image)  # byte to b64 byte
+                    s = base64_data.decode()  # byte to str
+                    d1['base_face_image_b64'] = f'data:image/jpeg;base64,{s}'
+
+                # --- fill face_type_name_list ---
+                face_type_uuid_list = face.get('face_type_uuid_list')
+                if face_type_uuid_list:
+                    d1['face_type_name_list'] = [face_type_name_dict.get(i)
+                                                 for i in face_type_uuid_list if face_type_name_dict.get(i)]
+
+                # --- append d1 ---
+                d3.append(d1)
+
+            # --- update call_id value ---
+            rdb.set_one(key=call_id, data=(d3, len(d2)), expire_time=600)
+            return dict(code=0, details=f"congratulations. | use time {methods.now_ts() - run_at}s")
+
+        except Exception as exception:
+
+            methods.debug_log('Task.task003', f"m-76: exception | {exception}")
+            methods.debug_log('Task.task003', f"m-76: traceback | {methods.trace_log()}")
+            return dict(code=-1, details=f"{methods.trace_log()}")

+ 36 - 0
sri-server-bg02/api/url.py

@@ -0,0 +1,36 @@
+from modules.command import Command
+from modules.task import Task
+from modules.test import TEST
+from modules.key import Key
+
+action_methods = {
+
+    # --- for command ---
+    'Command.cmd001': (Command, 'cmd001', 'NoBack'),  # update code by git commit
+    'Command.cmd002': (Command, 'cmd002', 'NoBack'),  # update pth by apache server
+    'Command.cmd003': (Command, 'cmd003', 'IsBack'),  # docker ps
+    'Command.cmd004': (Command, 'cmd004', 'IsBack'),  # docker images
+
+    # --- 初始化操作接口 ---
+    'Key.create_key': (Key, 'create_key', 'IsBack'),  # 生成key
+    'Key.verify_key': (Key, 'verify_key', 'IsBack'),  # 验证key
+
+    # --- for task ---
+    'Task.task002': (Task, 'task002', 'IsBack'),  # 接收 fastapi 发送来的 uuid 并计算人脸存储特征 同样提供判断该照片是否能用
+    'Task.task003': (Task, 'task003', 'IsBack'),  # 为8301接口提供业务支持
+
+    # --- for test ---
+    'TEST.test002': (TEST, 'test002', 'NoBack'),  # 刷超分人脸图片接口 将超分图片加入到底库中
+    'TEST.test001': (TEST, 'test001', 'NoBack'),  # 批量导入人脸特征数据 from mariadb to mongodb
+    'TEST.test004': (TEST, 'test004', 'NoBack'),  # 更新 mongo.VisitorTags 表数据
+    'TEST.test006': (TEST, 'test006', 'IsBack'),  # 对比人脸特征
+    'TEST.test007': (TEST, 'test007', 'IsBack'),  # 测试websocket在线信息
+    'TEST.test008': (TEST, 'test008', 'IsBack'),  # test
+    'TEST.test009': (TEST, 'test009', 'IsBack'),  # test
+    'TEST.test011': (TEST, 'test011', 'IsBack'),  # 下载语音文件
+    'TEST.test012': (TEST, 'test012', 'IsBack'),  # 录入人脸图片
+    'TEST.test013': (TEST, 'test013', 'IsBack'),  # 对比人脸图像的相似度
+    'TEST.test014': (TEST, 'test014', 'IsBack'),  # 对比人脸图像的相似度
+    'TEST.test015': (TEST, 'test015', 'IsBack'),  # 检索相似人脸
+
+}

+ 37 - 0
sri-server-bg02/compose.yml

@@ -0,0 +1,37 @@
+version: '3.5'
+services:
+
+    sri-module-bg02:
+
+        # --- building ---
+        image: sri-module-bg02:2024
+        build:
+            context: ./
+            dockerfile: ./Dockerfile
+        environment:
+            TZ: Asia/Shanghai
+
+        # --- binding ---
+        volumes:
+            - /home:/home
+        networks:
+            - sri_network
+        ports:
+            - 9102:9000
+
+        # --- running ---
+        container_name: sri-module-bg02
+
+        # --- for debug ---
+#        working_dir: /home/server/repositories/repositories/gitee.com/casperz.py-project/project-responder-bg
+#        stdin_open: true
+#        tty: true
+
+        # --- for release ---
+        working_dir: /home/server/repositories/repositories/gitee.com/casperz.py-project/project-responder-bg
+        command: bash run.sh
+        restart: always
+
+networks:
+    sri_network:
+        external: true

+ 17 - 0
sri-server-bg02/hub.py

@@ -0,0 +1,17 @@
+import sys
+import importlib
+
+sys.path.append('../3rdparty')
+
+methods = importlib.import_module(f"xlib")
+
+
+class Global(object):
+    # --- 中间件服务器 ---
+    emqx = importlib.import_module(f"xclient.xmqtt").Client(host='10.10.61.229', port=41883)
+
+    aps = importlib.import_module(f"xpip.xapscheduler").APS(db_type='mongo',
+                                                            db_host='58.34.94.176',
+                                                            db_port=7030,
+                                                            username='admin', password='admin',
+                                                            database='bg', collection='LoopTask')

+ 46 - 0
sri-server-bg02/lib/JobManage.py

@@ -0,0 +1,46 @@
+from hub import Global, methods
+
+
+class JobManage(object):
+    """"""
+
+    # --- 接口认证信息 ---
+    # username = '500A7062'
+    # password = '198797#cjhxbin'
+
+    @classmethod
+    def run(cls):
+        """启动全部任务"""
+
+        # --- test ---
+        # Global.aps.create_job(func=cls.job101, trigger='interval', seconds=3)  # 循环测试
+        # Global.aps.create_job(func=cls.job101, trigger='date', run_date='2022-02-23 18:54:00')  # 定时测试
+        # Global.aps.create_job(func=cls.job301, trigger='interval', seconds=60)  # 循环测试
+        # Global.aps.create_job(func=cls.job20102, trigger='date', run_date='2022-07-28 17:15:30')  # 定时测试
+
+        # --- release ---
+        Global.aps.create_job(func=cls.job101, trigger='cron', hour=22)  # 每天晚10点  release
+        # Global.aps.create_job(func=cls.job20102, trigger='interval', seconds=600)  # 每10分钟  release
+        # Global.aps.create_job(func=cls.job301, trigger='interval', seconds=300)  # 每5分钟  release
+
+    @classmethod
+    def end(cls):
+        """暂停全部任务"""
+        Global.aps.pause_all()
+
+    @staticmethod
+    def job101():
+        """
+        每日22点清理30天之前的日志
+        """
+        # --- get list ---
+        log_file_dir = f"/home/server/logs"
+        file_path_list = methods.get_file_path_list(log_file_dir)
+        file_name_list = [i.split('/')[-1] for i in file_path_list]
+        # methods.debug_log('JobManage.job101.41', f"#file_name_list: {file_name_list}")
+
+        # --- cut list ---
+        for file_name in file_name_list[30:]:
+            file_path = f"/home/server/logs/{file_name}"
+            if methods.is_file(file_path):
+                methods.remove_file(file_path)

+ 49 - 0
sri-server-bg02/lib/MessageListener.py

@@ -0,0 +1,49 @@
+from hub import methods, Global
+
+import threading
+import time
+import json
+
+
+class MessageListener(object):
+    """音柱循环检查"""
+
+    @staticmethod
+    def decorate_method(client, userdata, message):
+        """消息处理方法"""
+        file_name = methods.now_string('%Y-%m-%d.log')
+        log_file_path = f"/home/server/logs/{file_name}"
+        log_dict = json.loads(message.payload)
+        # methods.debug_log(f"MessageListener.20", f"#log_dict: {log_dict}")
+
+        log_list = list()
+        for i in range(1, 5):
+            v = str(log_dict.get(str(i)))
+            log_list.append(v)
+        methods.write_text(log_file_path, '|'.join(log_list) + '\n', 'a')
+        methods.debug_log(f"MessageListener.24", f"#message.payload: {json.loads(message.payload)}")
+
+    @classmethod
+    def start_check_loop(cls):
+
+        # --- check ---
+        save_dir = f"/home/server/logs"
+        if not methods.is_dir(save_dir):
+            out = methods.run_command(f'mkdir -p {save_dir}', callback=True)
+            methods.debug_log('MessageListener.33', f"#out: {out}")
+
+        Global.emqx.start_subscribe_loop(
+            decorate_method=MessageListener.decorate_method,
+            subscribe_topic='bg/log'
+        )
+
+    @classmethod
+    def run_background(cls, background_is=True):
+        """"""
+        p1 = threading.Thread(target=cls.start_check_loop)
+        p1.start()
+
+
+if __name__ == '__main__':
+    # --- test ---
+    MessageListener.run_background()

+ 252 - 0
sri-server-bg02/lib/line_manage.py

@@ -0,0 +1,252 @@
+"""
+websocket发数据
+"""
+from hub import methods, Global, numpy_method
+
+import cv2
+import base64
+import asyncio
+import threading
+
+
+class LineManage(object):
+    """"""
+
+    line_dict = {}  # {<line_id>: <ws>} | line_id: websocket连接id | ws: websocket链接对象
+
+    @classmethod
+    def run_forever(cls):
+        """
+        调用协程方法
+        """
+        tasks = [cls.check_send()]
+        _loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(_loop)
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(asyncio.wait(tasks))
+
+    @classmethod
+    def run_background(cls, is_back_run=True):
+        """
+        后台运行
+        """
+        t1 = threading.Thread(target=cls.run_forever)
+        t1.start()
+
+    @classmethod
+    async def check_send(cls):
+
+        # --- define ---
+        last_send_id = str()
+
+        while True:
+
+            try:
+                # --- fill face_type_name_dict ---
+                """
+                face_type_name_dict = {<type_uuid>: <name>}
+                """
+                face_type_name_dict = dict()
+                for item in Global.mdb.get_all('FaceType'):
+                    uuid = str(item.get('_id'))
+                    face_type_name_dict[uuid] = item.get('name')
+
+                # --- debug ---
+                # methods.debug_log(f"LineManage", f"m-21: run at {methods.now_string()} "
+                #                                  f"| {len(cls.line_dict.values())}")
+                # await asyncio.sleep(3)
+                # await asyncio.sleep(0.5)
+
+                # --- get send_data ---
+                """
+                send_data = {
+                    send_id: 数据id
+                    send_list: 数据列表
+                }
+                """
+                send_data = Global.rdb.get_one(key='send_data')
+                # send_data = db0.get_one(key='send_data')
+
+                # --- check ---
+                if not send_data:
+                    continue
+
+                # --- check ---
+                send_id = send_data.get('send_id')
+                if not send_id:
+                    continue
+
+                # --- check ---
+                if send_id == last_send_id:
+                    continue
+
+                # --- check ---
+                send_list = send_data.get('send_list')
+                if send_list is None or len(send_list) == 0:
+                    continue
+
+                # --- debug ---
+                # await asyncio.sleep(3)
+                # await asyncio.sleep(0.5)
+                methods.debug_log(f"LineManage", f"m-74: run at {methods.now_string()} "
+                                                 f"| send count is {len(send_list)} "
+                                                 f"| online count is {len(cls.line_dict.values())}")
+
+                # --- update ---
+                last_send_id = send_id
+
+                # --- send ---
+                for line_id in list(cls.line_dict.keys()):
+
+                    try:
+
+                        # --- check ---
+                        if not cls.check_line_is_live(line_id):
+                            methods.debug_log(f"LineManage", f"m-56: websocket link broken.")
+                            cls.line_dict.pop(line_id)
+                            continue
+
+                        # --- send ---
+                        """
+                        send_list = [
+                            {
+                                base_face_uuid: 底库人脸id
+                                snap_face_image: 抓拍人脸
+                                base_face_image_path: 底库人脸路径
+                                face_similarity: 相似度
+                            }
+                        ]
+                        """
+                        for data in send_list:
+
+                            # --- check ---
+                            if data.get('snap_face_image') is None:
+                                continue
+
+                            # --- define ---
+                            """
+                            send_dict = {
+                                input_face_b64: 抓拍人脸图像
+                                face_uuid: 人脸id
+                                face_name: 人脸名称
+                                known_face_b64: 底库人脸图像
+                                face_similarity: 相似度
+                                face_type_name_list: 人员类型
+                            }
+                            """
+                            send_dict = dict(
+                                input_face_b64=cls.image_to_b64(data.get('snap_face_image')),
+                                # input_face_b64=str(),
+                                known_face_b64=str(),
+                                face_uuid=str(),
+                                face_name=str(),
+                                face_similarity=data.get('face_similarity'),
+                                face_type_name_list=list(),
+                            )
+
+                            # --- fill input_face_b64 ---
+                            # snap_face_image_path = data.get('snap_face_image_path')
+                            # if snap_face_image_path and methods.is_file(snap_face_image_path):
+                            #     frame = cv2.imread(snap_face_image_path)
+                            #     if frame is not None:
+                            #         _, image = cv2.imencode('.jpg', frame)
+                            #         base64_data = base64.b64encode(image)  # byte to b64 byte
+                            #         s = base64_data.decode()  # byte to str
+                            #         send_dict['input_face_b64'] = f'data:image/jpeg;base64,{s}'
+
+                            # --- fill known_face_b64 ---
+                            base_face_image_path = data.get('base_face_image_path')
+                            if base_face_image_path and methods.is_file(base_face_image_path):
+                                frame = cv2.imread(base_face_image_path)
+                                if frame is not None:
+                                    _, image = cv2.imencode('.jpg', frame)
+                                    base64_data = base64.b64encode(image)  # byte to b64 byte
+                                    s = base64_data.decode()  # byte to str
+                                    send_dict['known_face_b64'] = f'data:image/jpeg;base64,{s}'
+
+                            # --- fill face_uuid and face_name ---
+                            """
+                            Face: 陌生人脸表
+                            Face.face_name: 人脸名称
+                            """
+                            face_uuid = data.get('base_face_uuid')
+                            if face_uuid:
+                                send_dict['face_uuid'] = face_uuid
+                                face = Global.mdb.get_one_by_id('Face', face_uuid)
+                                if face and face.get('face_name'):
+                                    send_dict['face_name'] = face.get('face_name')
+
+                                # --- fill face_type_name_list ---
+                                face_type_uuid_list = face.get('face_type_uuid_list')
+                                if face_type_uuid_list:
+                                    send_dict['face_type_name_list'] = [face_type_name_dict.get(i)
+                                                                        for i in face_type_uuid_list
+                                                                        if face_type_name_dict.get(i)]
+
+                            # --- send ---
+                            # methods.debug_log(f"LineManage", f"m-153: send_dict is {send_dict}")
+                            line = cls.line_dict.get(line_id)
+                            send_json = methods.json_dumps(send_dict)
+                            await line.send_text(send_json)
+                            # await asyncio.sleep(0.1)
+
+                    except Exception as exception:
+
+                        # --- check ---
+                        if not cls.check_line_is_live(line_id):
+                            cls.line_dict.pop(line_id)
+
+                        if exception.__class__.__name__ == 'RuntimeError':
+                            methods.debug_log(f"LineManage", f"m-170: {cls.get_line_state()}")
+                        else:
+                            methods.debug_log('LineManage', f"m-172: exception | {exception}")
+                            methods.debug_log('LineManage', f"m-172: traceback | {methods.trace_log()}")
+
+            except Exception as exception:
+
+                methods.debug_log('LineManage', f"m-179: exception | {exception}")
+                methods.debug_log('LineManage', f"m-179: traceback | {methods.trace_log()}")
+                methods.debug_log('LineManage', f"m-179: wait 1 minutes try again!")
+                await asyncio.sleep(60)
+
+    @classmethod
+    def get_line_total(cls):
+        count = 0
+        for k, v in cls.line_dict.items():
+            count += 1
+        return count
+
+    @classmethod
+    def check_line_is_live(cls, line_id):
+        d1 = {
+            0: 'CONNECTING',
+            1: 'CONNECTED',
+            2: 'DISCONNECTED',
+        }
+        line = cls.line_dict.get(line_id)
+        if line and d1.get(line.client_state.value) != 'DISCONNECTED':
+            return True
+        else:
+            return False
+
+    @classmethod
+    def get_line_state(cls):
+        d1 = {
+            0: 'CONNECTING',
+            1: 'CONNECTED',
+            2: 'DISCONNECTED',
+        }
+        d2 = dict()  # {<line_id>: <state>}
+        for line_id, line in cls.line_dict.items():
+            state = d1.get(line.client_state.value)
+            _id = line_id[-6:]
+            d2[_id] = state
+        return d2
+
+    @staticmethod
+    def image_to_b64(image):
+        frame = numpy_method.to_array(image)  # list to numpy array
+        _, image = cv2.imencode('.jpg', frame)
+        base64_data = base64.b64encode(image)
+        s = base64_data.decode()
+        return f'data:image/jpeg;base64,{s}'

+ 159 - 0
sri-server-bg02/lib/sound_columns.py

@@ -0,0 +1,159 @@
+from hub import methods, Global
+
+import threading
+import time
+
+
+class SoundColumns(object):
+    """音柱循环检查"""
+
+    mdb = Global.get_mongodb_client()
+    api = Global.get_audio_client()
+
+    @classmethod
+    def get_mp3_name_dict(cls):
+        mp3_name_dict = dict()
+        """
+        FaceType: 人脸类型表
+        FaceType.name: 类型名称
+        FaceType.mp3_name: 音频名称
+        """
+        for item in Global.mdb.get_all('FaceType'):
+            uuid = str(item.get('_id'))
+            mp3_name_dict[uuid] = item.get('mp3_name')
+        return mp3_name_dict
+
+    @classmethod
+    def get_column_service_url_and_sn(cls):
+        """
+        获取音柱信息 todo 建议增加ip校验
+        """
+        unique_dict = {'name': 'AudioConfig'}
+        item = cls.mdb.get_one('GlobalVariable', unique_dict)
+        data = item.get('args', {})
+        audio_sn = data.get('audio_sn')
+        audio_url = f"http://{data.get('audio_ipv4')}:{data.get('audio_port')}"
+        audio_vol = data.get('audio_vol')
+        return audio_url, audio_sn, audio_vol
+
+    @classmethod
+    def get_host_ip(cls):
+        """
+        获取本机ip
+        """
+        unique_dict = {'name': 'HostIpConfig'}
+        item = cls.mdb.get_one('GlobalVariable', unique_dict)
+        data = item.get('args', {})
+        host_ip = data.get('ipv4')
+
+        # --- check ---
+        if not host_ip:
+            ssh = Global.SSHClient('172.18.0.1', 22, 'server', 'server')
+            out = ssh.run_command("ifconfig")
+            for row in out.split('\n\n'):
+                if not row.startswith('eth0'):  # eth0 enp0s3
+                    continue
+                for one in row.split('\n'):
+                    one = one.strip()
+                    if one[:4] != 'inet':
+                        continue
+                    if one[:5] == 'inet6':
+                        continue
+                    one = [i for i in one.split(' ') if i]
+                    ipv4, netmask = one[1], one[3]
+                    host_ip = ipv4
+        return host_ip
+
+    @classmethod
+    def check_loop(cls):
+
+        # --- define ---
+        last_send_id = str()
+
+        while True:
+
+            # --- check --- 检查是否配置音柱
+            # while True:
+            #     unique_dict = {'name': 'AudioConfig'}
+            #     item = cls.mdb.get_one('GlobalVariable', unique_dict)
+            #     data = item.get('args', {})
+            #     audio_ipv4 = data.get('audio_ipv4')
+            #     if audio_ipv4:
+            #         break
+            #     else:
+            #         time.sleep(60)
+
+            try:
+
+                # --- get send_data ---
+                send_data = Global.rdb.get_one(key='send_data')
+
+                # --- check ---
+                if not send_data:
+                    continue
+
+                # --- check ---
+                send_id = send_data.get('send_id')
+                if not send_id:
+                    continue
+
+                # --- check ---
+                if send_id == last_send_id:
+                    continue
+
+                # --- check ---
+                """
+                send_list = [
+                    {
+                        base_face_uuid: 底库人脸id
+                        snap_face_image: 抓拍人脸
+                        base_face_image_path: 底库人脸路径
+                        face_similarity: 相似度
+                    }
+                ]
+                """
+                send_list = send_data.get('send_list')
+                if send_list is None or len(send_list) == 0:
+                    continue
+
+                # --- debug ---
+                # methods.debug_log(f"SoundColumns", f"m-122: run at {methods.now_string()} "
+                #                                    f"| send count is {len(send_list)} ")
+
+                # --- update ---
+                last_send_id = send_id
+
+                # --- call --- todo 建议界面上添加个开关,是否启用音响
+                """增加音柱配置写活"""
+                audio_url, audio_sn, audio_vol = cls.get_column_service_url_and_sn()
+                cls.api.api_service_url = audio_url
+                # cls.api.sn = audio_sn
+                cls.api.vol = audio_vol
+
+                host_ip = cls.get_host_ip()
+                cls.api.file_service_url = f'http://{host_ip}:9900'
+                methods.debug_log(f"SoundColumns", f"m-96: file_service_url -> {cls.api.file_service_url}")
+
+                for data in send_list:
+                    result_type = data.get('result_type')
+                    # result = cls.api.call_audio_make_sound_v3(result_type)
+                    result = cls.api.call_audio_make_sound_v4(result_type)
+                    methods.debug_log(f"SoundColumns", f"m-131: result is {result} | run at {methods.now_string()} |"
+                                                       f"send count is {len(send_list)}")
+
+            except Exception as exception:
+
+                methods.debug_log('SoundColumns', f"m-153: exception | {exception}")
+                methods.debug_log('SoundColumns', f"m-153: wait 10 minutes try again!")
+                time.sleep(600)
+                continue
+
+    @classmethod
+    def run_background(cls, is_back_run=True):
+        """"""
+        p1 = threading.Thread(target=cls.check_loop)
+        p1.start()
+
+
+if __name__ == '__main__':
+    SoundColumns.run_background()

+ 22 - 0
sri-server-bg02/main.py

@@ -0,0 +1,22 @@
+# from app import generate_app
+# app = generate_app()
+
+def main():
+    # 定时任务
+    from lib.JobManage import JobManage
+    JobManage.run()
+
+    # 监听mqtt消息服务
+    from lib.MessageListener import MessageListener
+    MessageListener.run_background(background_is=False)
+
+    # 给websocket发数据的
+    # from factories.line_manage import LineManage
+    # LineManage.run_background()
+
+    # websocket服务 与 api服务
+    # app.run(address='0.0.0.0', port=5042, debug=True)
+
+
+if __name__ == '__main__':
+    main()

+ 5 - 0
sri-server-bg02/run.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+echo "BEGIN:" \
+&& python3 main.py
+

Vissa filer visades inte eftersom för många filer har ändrats