zhangnaiwen 2 жил өмнө
parent
commit
4701e5ff1e

+ 5 - 0
Dockerfile

@@ -0,0 +1,5 @@
+FROM operation_management_center_base:latest
+
+COPY ./src /work/operation_management_center/src
+
+WORKDIR /work/operation_management_center/src

+ 31 - 0
base/Dockerfile

@@ -0,0 +1,31 @@
+FROM debian11_py3.11:latest
+
+COPY ./requirements.txt /tmp/
+COPY ./conf/bootstrap.sh /opt/bootstrap.sh
+COPY ./conf/uwsgi.ini /work/operation_management_center/conf/uwsgi.ini
+COPY ./conf/supervisord.conf /etc/
+COPY ./conf/supervisor.conf /etc/supervisord.d/supervisor.conf
+
+RUN apt-get -y update \
+    && apt-get -y upgrade \
+    && apt-get install -y supervisor libpq-dev python-dev\
+    && pip3.11 install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple \
+    && pip3.11 install uwsgi -i https://pypi.tuna.tsinghua.edu.cn/simple \
+    # 默认是128,当server处理请求较慢,以至于socket监听队列被填满后,新来的请求会被拒绝。
+    && mkdir /opt/init  \
+    && echo "echo 2048 > /proc/sys/net/core/somaxconn" > /opt/init/sysctl.sh  \
+    && chmod +x /opt/bootstrap.sh \
+    # 清理和删除工作
+    && apt-get autoremove -y  \
+    && apt-get autoclean -y \
+    && apt-get clean -y \
+    && rm -rf /tmp/* /var/tmp/*  \
+    && find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete  \
+    && find /var/cache -type f -delete \
+    && find /var/log -type f | while read f; do echo -n '' > ${f}; done \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf ~/.cache/pip/*
+
+ENTRYPOINT ["/opt/bootstrap.sh"]
+
+EXPOSE 5000

+ 47 - 0
conf/bootstrap.sh

@@ -0,0 +1,47 @@
+#!/bin/bash
+
+set -e
+set -u
+
+# supervisord 配置文件
+SUPERVISOR_PARAMS='-c /etc/supervisord.conf'
+
+
+# 创建一些需要的目录
+mkdir -p /data/conf /data/run /data/logs/
+chmod 711 /data/conf /data/run /data/logs
+
+#export WORKER_REDIS_PORT=${REDIS_PORT:6}
+#export WORKER_REDIS_DB=${REDIS_DB:-0}
+
+# 遍历 `/opt/init/*.sh`,然后执行它
+if [ "$(ls /opt/init/)" ]; then
+  for cmd in /opt/init/*.sh; do
+    . $cmd
+  done
+fi
+
+
+# 可能会使用一个交互式的容器.
+if test -t 0; then
+  # 运行 supervisord 到后台
+  supervisord $SUPERVISOR_PARAMS
+
+  # 运行一些命令并退出
+  # 没有命令时,运行bash
+  if [[ $@ ]]; then
+    eval $@
+  else
+    export PS1='[\u@\h : \w]\$ '
+    /bin/bash
+  fi
+
+# 运行 supervisord 在前台, 保持直到停止容器.
+else
+  # 有额外的参数,先执行它.
+  # 可能会有些问题
+  if [[ $@ ]]; then
+    eval $@
+  fi
+  supervisord -n $SUPERVISOR_PARAMS
+fi

+ 8 - 0
conf/supervisor.conf

@@ -0,0 +1,8 @@
+[program:operation_management_center]
+command = uwsgi /work/operation_management_center/conf/uwsgi.ini
+
+autorestart = true
+redirect_stderr = true
+stopsignal = QUIT
+stderr_logfile = /data/logs/operation_management_center_error.log
+stdout_logfile = /data/logs/operation_management_center.log

+ 18 - 0
conf/supervisord.conf

@@ -0,0 +1,18 @@
+[supervisord]
+pidfile = /run/supervisord.pid
+logfile = /data/logs/supervisord.log
+# Set loglevel=debug, only then all logs from child services are printed out
+# to container logs (and thus available via `docker logs [container]
+loglevel = debug
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[inet_http_server]
+port:127.0.0.1:9999
+
+[supervisorctl]
+serverurl=http://127.0.0.1:9999
+
+[include]
+files = /etc/supervisord.d/*.conf

+ 125 - 0
conf/uwsgi.ini

@@ -0,0 +1,125 @@
+[uwsgi]
+# 指定应用的用户(组)
+if-env = UWSGI_USER
+uid = $(UWSGI_USER)
+endif =
+
+if-env = UWSGI_GROUP
+gid = $(UWSGI_GROUP)
+endif =
+
+# 监听的ip与端口
+http = :5000
+if-env = UWSGI_HTTP
+http = $(UWSGI_HTTP)
+endif =
+
+# PID文件
+pidfile = /var/run/operation_management_center.pid
+
+# 主入口模块
+module = app.webapp
+
+# 主入口函数
+callable = application
+
+# 需要关闭master,否则会导致进程中使用全局变量会出错
+master = true
+
+# 这个会导致不正常,待测试
+#async=128
+
+# 进程数
+processes = 2
+if-env = UWSGI_PROCESSES
+processes = $(UWSGI_PROCESSES)
+endif =
+
+# 运行多线程
+enable-threads = 0
+if-env = UWSGI_ENABLE_THREADS
+enable-threads = $(UWSGI_ENABLE_THREADS)
+endif =
+
+# http的线程数
+http-processes = 2
+if-env = UWSGI_HTTP_PROCESSES
+http-processes = $(UWSGI_HTTP_PROCESSES)
+endif =
+
+# 是否保持http连接
+http-keepalive = 0
+if-env = UWSGI_HTTP_KEEPALIVE
+http-keepalive = $(UWSGI_HTTP_KEEPALIVE)
+endif =
+
+# 超时关闭连接的时间(秒)
+harakiri = 20
+if-env = UWSGI_HARAKIRI
+harakiri = $(UWSGI_HARAKIRI)
+endif =
+
+# 打开http body缓冲
+if-env = UWSGI_POST_BUFFERING
+post-buffering = $(UWSGI_POST_BUFFERING)
+endif =
+
+# python解释器的优化,=0不优化
+if-env = UWSGI_PY_OPTIMIZE
+optimize = $(UWSGI_PY_OPTIMIZE)
+endif =
+
+# 关闭http请求的日志
+if-env = UWSGI_DISABLE_LOGGING
+disable-logging = $(UWSGI_DISABLE_LOGGING)
+endif =
+
+# 记录uwsgi自身的日志
+logto = /data/logs/operation_management_center.log
+# daemonize = /data/logs/nginx_agent.log
+
+
+# 等待其它进程重启(直到接收到的请求处理完才重启)/关闭的最大时间(秒)
+reload-mercy = 2
+
+# CPU亲和性
+cpu-affinity = 1
+
+# 进程的内存限制(address space/vsz)
+if-env = UWSGI_LIMIT_AS
+limit-as = $(UWSGI_LIMIT_AS)
+endif =
+
+# 占用内存(address space)大于指定值(MB),重启服务
+if-env = UWSGI_RELOAD_ON_AS
+reload-on-as = $(UWSGI_RELOAD_ON_AS)
+endif =
+
+# 占用内存(rss)大于指定值(MB),重启服务
+if-env = UWSGI_RELOAD_ON_RSS
+reload-on-rss = $(UWSGI_RELOAD_ON_RSS)
+endif =
+
+# 超过指定请求数,会创建新的进程
+if-env = UWSGI_MAX_REQUESTS
+max-requests = $(UWSGI_MAX_REQUESTS)
+endif =
+
+# 退出时清除环境(自动删除unix socket文件和pid文件)
+vacuum = true
+
+# python代码变化后自动重启服务,仅用用开发环境
+if-env = UWSGI_PY_AUTORELOAD
+py-autoreload = $(UWSGI_PY_AUTORELOAD)
+endif =
+
+# master进程关闭会自动杀死workers
+no-orphans = true
+
+# 设置socket的监听队列大小
+if-env = UWSGI_LISTEN
+listen = $(UWSGI_LISTEN)
+endif =
+
+# python包环境
+pythonpath=/work/operation_management_center/src

+ 301 - 0
doc/menuList.json

@@ -0,0 +1,301 @@
+[
+  {
+    "name": "智能看板",
+    "router": "/dashboard",
+    "icon": "智能看板.png"
+  },
+  {
+    "name": "智享生活",
+    "router": "/life",
+    "icon": "智享生活.png",
+    "children": [
+      {
+        "name": "智慧餐厅",
+        "router": "/life/restaurant",
+        "icon": "智慧餐厅.png"
+      },
+      {
+        "name": "智慧停车",
+        "router": "/life/parking",
+        "icon": "智慧停车.png"
+      },
+      {
+        "name": "无人商超",
+        "router": "/life/supermarket",
+        "icon": "无人商超.png"
+      },
+      {
+        "name": "健康小屋",
+        "router": "/life/healthyHome",
+        "icon": "健康小屋.png"
+      }
+    ]
+  },
+  {
+    "name": "智慧办公",
+    "router": "/work",
+    "icon": "智慧办公.png",
+    "children": [
+      {
+        "name": "楼层概览",
+        "router": "/work/overview",
+        "icon": "楼层概览.png"
+      },
+      {
+        "name": "会议管理",
+        "router": "/work/meeting",
+        "icon": "会议管理.png"
+      },
+      {
+        "name": "文印管理",
+        "router": "/work/print",
+        "icon": "文印管理.png"
+      },
+      {
+        "name": "公车管理",
+        "router": "/work/bus",
+        "icon": "公车管理.png"
+      }
+    ]
+  },
+  {
+    "name": "数智双碳",
+    "router": "/doubleCarbon",
+    "icon": "数智双碳.png",
+    "children": [
+      {
+        "name": "双碳概览",
+        "router": "/doubleCarbon/overview",
+        "icon": "数智双碳.png"
+      },
+      {
+        "name": "车辆排放",
+        "router": "/doubleCarbon/car",
+        "icon": "车辆排放.png"
+      },
+      {
+        "name": "文印排放",
+        "router": "/doubleCarbon/print",
+        "icon": "文印排放.png"
+      },
+      {
+        "name": "光伏发电",
+        "router": "/doubleCarbon/pv",
+        "icon": "光伏发电.png"
+      }
+    ]
+  },
+  {
+    "name": "智慧运营",
+    "router": "/business",
+    "icon": "智慧运营.png",
+    "children": [
+      {
+        "name": "资产管理",
+        "router": "/business/asset",
+        "icon": "资产管理.png"
+      },
+      {
+        "name": "空间管理",
+        "router": "/business/space",
+        "icon": "空间管理.png"
+      },
+      {
+        "name": "运营分析",
+        "router": "/business/analysis",
+        "icon": "能源管理.png"
+      }
+    ]
+  },
+  {
+    "name": "智慧安防",
+    "router": "/security",
+    "icon": "智慧安防.png",
+    "children": [
+      {
+        "name": "安防人员",
+        "router": "/security/person",
+        "icon": "安防人员.png"
+      },
+      {
+        "name": "安消联动",
+        "router": "/security/alarm",
+        "icon": "安消联动.png",
+        "children": [
+          {
+            "name": "地图模式",
+            "router": "/security/alarm/map",
+            "icon": ""
+          },
+          {
+            "name": "宫格模式",
+            "router": "/security/alarm/grid",
+            "icon": ""
+          },
+          {
+            "name": "告警管理",
+            "router": "/security/alarm/manage",
+            "icon": ""
+          },
+          {
+            "name": "告警库",
+            "router": "/security/alarm/info",
+            "icon": ""
+          }
+        ]
+      },
+      {
+        "name": "设备交互",
+        "router": "/security/device",
+        "icon": "设备交互.png"
+      }
+    ]
+  },
+  {
+    "name": "智慧场景",
+    "router": "/scene",
+    "icon": "智慧场景.png",
+    "children": [
+      {
+        "name": "智•会议",
+        "router": "/scene/meeting",
+        "icon": "智会议.png",
+        "children": [
+          {
+            "name": "配置会议",
+            "router": "/scene/meeting/config",
+            "icon": ""
+          },
+          {
+            "name": "统计分析",
+            "router": "/scene/meeting/overview",
+            "icon": ""
+          }
+        ]
+      },
+      {
+        "name": "智•能源",
+        "router": "/scene/energy",
+        "icon": "智能源.png",
+        "children": [
+          {
+            "name": "智能空调",
+            "router": "/scene/energy/aircondition",
+            "icon": ""
+          },
+          {
+            "name": "智能照明",
+            "router": "/scene/energy/light",
+            "icon": ""
+          },
+          {
+            "name": "智能饮水机",
+            "router": "/scene/energy/water",
+            "icon": ""
+          }
+        ]
+      },
+      {
+        "name": "智•安防",
+        "router": "/scene/access",
+        "icon": "智安防.png",
+        "children": [
+          {
+            "name": "门禁管理",
+            "router": "/scene/access/manage",
+            "icon": ""
+          },
+          {
+            "name": "访客管理",
+            "router": "/scene/access/guest",
+            "icon": ""
+          },
+          {
+            "name": "停车管理",
+            "router": "/scene/access/park",
+            "icon": ""
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "name": "数据报表",
+    "router": "/report",
+    "icon": "数据报表.png",
+    "children": [
+      {
+        "name": "报表",
+        "router": "/report/table",
+        "icon": "报表.png",
+        "children": [
+          {
+            "name": "定制化消费报表",
+            "router": "/report/table/custom",
+            "icon": ""
+          },
+          {
+            "name": "智享生活报表",
+            "router": "/report/table/life",
+            "icon": ""
+          },
+          {
+            "name": "智慧办公报表",
+            "router": "/report/table/work",
+            "icon": ""
+          },
+          {
+            "name": "数智双碳报表",
+            "router": "/report/table/carbon",
+            "icon": ""
+          },
+          {
+            "name": "智慧运营报表",
+            "router": "/report/table/operation",
+            "icon": ""
+          },
+          {
+            "name": "智慧安防报表",
+            "router": "/report/table/security",
+            "icon": ""
+          }
+        ]
+      },
+      {
+        "name": "报告",
+        "router": "/report/data",
+        "icon": "报告.png",
+        "children": [
+          {
+            "name": "餐厅消费报告",
+            "router": "/report/data/restaurant",
+            "icon": ""
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "name": "统一鉴权",
+    "router": "/auth",
+    "icon": "权限下达.png",
+    "children": [
+      {
+        "name": "角色权限",
+        "router": "/auth/role",
+        "icon": "角色权限.png"
+      },
+      {
+        "name": "行为权限",
+        "router": "/auth/action",
+        "icon": "行为权限.png"
+      },
+      {
+        "name": "权限查看",
+        "router": "/auth/view",
+        "icon": "权限查看.png"
+      }
+    ]
+  }
+
+]

+ 24 - 0
docker-compose-v3.yml

@@ -0,0 +1,24 @@
+version: '3'
+services:
+  sky-gistools-server:
+      container_name: operation_management_center
+
+      image: operation_management_center:1.0.0
+
+      ports:
+        - "5001:5000"
+
+      environment:
+        - UWSGI_PROCESSES=8
+        - UWSGI_ENABLE_THREADS=true
+        - UWSGI_HTTP_PROCESSES=4
+        - UWSGI_HTTP_KEEPALIVE=false
+        - UWSGI_HARAKIRI=600
+        - UWSGI_PY_OPTIMIZE=0
+
+      volumes:
+        - /data:/data
+
+      privileged: true
+
+      restart: always

+ 22 - 0
docker-compose.yml

@@ -0,0 +1,22 @@
+service:
+  container_name: operation_management_center
+
+  image: operation_management_center:1.0.0
+
+  ports:
+    - "5000:5000"
+
+  environment:
+    - UWSGI_PROCESSES=8
+    - UWSGI_ENABLE_THREADS=true
+    - UWSGI_HTTP_PROCESSES=4
+    - UWSGI_HTTP_KEEPALIVE=false
+    - UWSGI_HARAKIRI=600
+    - UWSGI_PY_OPTIMIZE=0
+
+  volumes:
+    - /data:/data
+
+  privileged: true
+
+  restart: always

+ 5 - 5
requirements.txt

@@ -1,23 +1,23 @@
 aniso8601==9.0.1
 aniso8601==9.0.1
-async-timeout==4.0.2
 attrs==22.2.0
 attrs==22.2.0
+cffi==1.15.1
 click==8.1.3
 click==8.1.3
+cryptography==39.0.2
 Flask==2.2.3
 Flask==2.2.3
 Flask-Cors==3.0.10
 Flask-Cors==3.0.10
-Flask-JWT==0.3.2
 flask-restx==1.1.0
 flask-restx==1.1.0
 greenlet==2.0.2
 greenlet==2.0.2
 itsdangerous==2.1.2
 itsdangerous==2.1.2
 Jinja2==3.1.2
 Jinja2==3.1.2
 jsonschema==4.17.3
 jsonschema==4.17.3
+jwt==1.3.1
 MarkupSafe==2.1.2
 MarkupSafe==2.1.2
 psycopg2==2.9.5
 psycopg2==2.9.5
-PyJWT==2.6.0
+pycparser==2.21
 pyrsistent==0.19.3
 pyrsistent==0.19.3
 pytz==2022.7.1
 pytz==2022.7.1
-redis==4.5.1
 six==1.16.0
 six==1.16.0
-SQLAlchemy==2.0.6
+SQLAlchemy==2.0.7
 SQLAlchemy-Utils==0.40.0
 SQLAlchemy-Utils==0.40.0
 typing_extensions==4.5.0
 typing_extensions==4.5.0
 Werkzeug==2.2.3
 Werkzeug==2.2.3

+ 2 - 0
src/app/api/__init__.py

@@ -7,6 +7,7 @@ from app.api.role import ns as role
 from app.api.data import ns as data
 from app.api.data import ns as data
 from app.api.log import ns as log
 from app.api.log import ns as log
 from app.api.message import ns as message
 from app.api.message import ns as message
+from app.api.permission import ns as permission
 
 
 api = Api(version='v1.0', title='operation_management_center', description='运营管理中心', doc='/api')
 api = Api(version='v1.0', title='operation_management_center', description='运营管理中心', doc='/api')
 
 
@@ -17,3 +18,4 @@ api.add_namespace(role)
 api.add_namespace(data)
 api.add_namespace(data)
 api.add_namespace(log)
 api.add_namespace(log)
 api.add_namespace(message)
 api.add_namespace(message)
+api.add_namespace(permission)

+ 2 - 1
src/app/api/permission.py

@@ -49,12 +49,13 @@ class AuthPermissionApi(Resource):
     method_decorators = [login_required]
     method_decorators = [login_required]
 
 
     @ns.doc(id='auth_permission', description='权限认证')
     @ns.doc(id='auth_permission', description='权限认证')
-    @ns.expect()
+    @ns.expect(auth_permission)
     def get(self):
     def get(self):
         """权限认证"""
         """权限认证"""
         path = request.args.get('path')
         path = request.args.get('path')
 
 
         with Session(engine) as session:
         with Session(engine) as session:
+            # todo role_id 写入token?
             # 根据用户id获取角色id
             # 根据用户id获取角色id
             stmt = select(User.role).where(User.id == g.user_id)
             stmt = select(User.role).where(User.id == g.user_id)
             role_id = session.execute(stmt).scalars().first()
             role_id = session.execute(stmt).scalars().first()

+ 22 - 15
src/app/configs/config.py

@@ -1,26 +1,33 @@
-JWT_SECRET = 'SKYversation0816'
-JWT_EXPIRY = 3600
+import json
+import os
 
 
+JWT_SECRET = str(os.environ.get('JWT_SECRET', 'SKYversation0816'))
+JWT_EXPIRY = int(os.environ.get('JWT_EXPIRY', 3600))
 
 
-MESSAGR_TYPE = ['普通消息','提醒消息']
-
+MESSAGR_TYPE = json.loads(os.environ.get('MESSAGR_TYPE', '["普通消息","提醒消息"]'))
 
 
 # 公司商标存储地址
 # 公司商标存储地址
-COMPANY_LOGO_PATH = "/Users/mac/data/company_logo"
-COMPANY_LOGO_URL = 'http://127.0.0.1/company_logo/'
+COMPANY_LOGO_PATH = os.environ.get('COMPANY_LOGO_PATH', "/data/company_logo")
+COMPANY_LOGO_URL = os.environ.get('COMPANY_LOGO_URL', 'http://127.0.0.1/company_logo/')
 
 
 # 模版存储地址
 # 模版存储地址
-TEMPLATE_FILE_PATH = '/Users/mac/data/template'
-TEMPLATE_FILE_URL = 'http://127.0.0.1/template/'
+TEMPLATE_FILE_PATH = os.environ.get('TEMPLATE_FILE_PATH', '/data/template')
+TEMPLATE_FILE_URL = os.environ.get('TEMPLATE_FILE_URL', 'http://127.0.0.1/template/')
 
 
 # 公司图片存醋地址
 # 公司图片存醋地址
-COMPANY_PICTURE_PATH = '/Users/mac/data/company'
-COMPANY_PICTURE_URL = 'http://127.0.0.1/company/'
+COMPANY_PICTURE_PATH = os.environ.get('COMPANY_PICTURE_PATH', '/data/company')
+COMPANY_PICTURE_URL = os.environ.get('COMPANY_PICTURE_URL', 'http://127.0.0.1/company/')
 
 
 # 楼宇图片存醋地址
 # 楼宇图片存醋地址
-BUILDING_PICTURE_PATH = '/Users/mac/data/building'
-BUILDING_PICTURE_URL = 'http://127.0.0.1/building/'
+BUILDING_PICTURE_PATH = os.environ.get('BUILDING_PICTURE_PATH', '/data/building')
+BUILDING_PICTURE_URL = os.environ.get('BUILDING_PICTURE_URL', 'http://127.0.0.1/building/')
+
+# 底层系统信息图片存醋地址
+UNDERLYING_SYSTEM_PICTURE_PATH = os.environ.get('UNDERLYING_SYSTEM_PICTURE_PATH', '/data/underlying_system')
+UNDERLYING_SYSTEM_PICTURE_URL = os.environ.get('UNDERLYING_SYSTEM_PICTURE_URL', 'http://127.0.0.1/underlying_system/')
+
+for path in [COMPANY_LOGO_PATH, TEMPLATE_FILE_PATH, COMPANY_PICTURE_PATH, BUILDING_PICTURE_PATH,
+             UNDERLYING_SYSTEM_PICTURE_PATH]:
 
 
-# 底层系统信息
-UNDERLYING_SYSTEM_PICTURE_PATH = '/Users/mac/data/underlying_system'
-UNDERLYING_SYSTEM_PICTURE_URL = 'http://127.0.0.1/underlying_system/'
+    if not os.path.exists(path):
+        os.makedirs(path)

+ 0 - 0
src/app/services/__init__.py