Ver código fonte

部署相关

zhangnaiwen 2 anos atrás
pai
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
-async-timeout==4.0.2
 attrs==22.2.0
+cffi==1.15.1
 click==8.1.3
+cryptography==39.0.2
 Flask==2.2.3
 Flask-Cors==3.0.10
-Flask-JWT==0.3.2
 flask-restx==1.1.0
 greenlet==2.0.2
 itsdangerous==2.1.2
 Jinja2==3.1.2
 jsonschema==4.17.3
+jwt==1.3.1
 MarkupSafe==2.1.2
 psycopg2==2.9.5
-PyJWT==2.6.0
+pycparser==2.21
 pyrsistent==0.19.3
 pytz==2022.7.1
-redis==4.5.1
 six==1.16.0
-SQLAlchemy==2.0.6
+SQLAlchemy==2.0.7
 SQLAlchemy-Utils==0.40.0
 typing_extensions==4.5.0
 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.log import ns as log
 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')
 
@@ -17,3 +18,4 @@ api.add_namespace(role)
 api.add_namespace(data)
 api.add_namespace(log)
 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]
 
     @ns.doc(id='auth_permission', description='权限认证')
-    @ns.expect()
+    @ns.expect(auth_permission)
     def get(self):
         """权限认证"""
         path = request.args.get('path')
 
         with Session(engine) as session:
+            # todo role_id 写入token?
             # 根据用户id获取角色id
             stmt = select(User.role).where(User.id == g.user_id)
             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