From 37ce56fd2145781dae6d5c412f74f6dab8314c6f Mon Sep 17 00:00:00 2001 From: wb-cp908446 Date: Fri, 19 Jan 2024 14:54:56 +0800 Subject: [PATCH] feat:add key_server and offline_push service --- deps/2_nginx/sysom.conf | 17 ++ script/server/sysom_key_server/clear.sh | 14 ++ script/server/sysom_key_server/db_migrate.sh | 18 ++ script/server/sysom_key_server/init.sh | 25 +++ script/server/sysom_key_server/install.sh | 29 +++ .../server/sysom_key_server/requirements.txt | 20 +++ script/server/sysom_key_server/start.sh | 29 +++ script/server/sysom_key_server/stop.sh | 8 + .../sysom_key_server/sysom-key_server.ini | 9 + script/server/sysom_key_server/uninstall.sh | 9 + script/server/sysom_offline_push/clear.sh | 14 ++ .../server/sysom_offline_push/db_migrate.sh | 18 ++ script/server/sysom_offline_push/init.sh | 25 +++ script/server/sysom_offline_push/install.sh | 29 +++ .../sysom_offline_push/requirements.txt | 21 +++ script/server/sysom_offline_push/start.sh | 29 +++ script/server/sysom_offline_push/stop.sh | 8 + .../sysom_offline_push/sysom-offline_push.ini | 9 + script/server/sysom_offline_push/uninstall.sh | 9 + sysom_server/sysom_key_server/alembic.ini | 102 +++++++++++ sysom_server/sysom_key_server/alembic/README | 1 + sysom_server/sysom_key_server/alembic/env.py | 115 ++++++++++++ .../sysom_key_server/alembic/script.py.mako | 24 +++ .../alembic/versions/.gitkeep | 0 sysom_server/sysom_key_server/app/__init__.py | 8 + sysom_server/sysom_key_server/app/crud.py | 36 ++++ sysom_server/sysom_key_server/app/database.py | 30 ++++ sysom_server/sysom_key_server/app/models.py | 24 +++ sysom_server/sysom_key_server/app/query.py | 28 +++ .../sysom_key_server/app/routers/aes.py | 39 ++++ .../sysom_key_server/app/routers/health.py | 21 +++ .../app/routers/key_service.py | 37 ++++ .../sysom_key_server/app/routers/person.py | 33 ++++ sysom_server/sysom_key_server/app/schemas.py | 24 +++ sysom_server/sysom_key_server/conf/common.py | 33 ++++ sysom_server/sysom_key_server/conf/develop.py | 15 ++ .../sysom_key_server/conf/gunicorn.py | 23 +++ sysom_server/sysom_key_server/conf/product.py | 15 ++ .../sysom_key_server/conf/settings.py | 19 ++ sysom_server/sysom_key_server/conf/testing.py | 14 ++ sysom_server/sysom_key_server/config.yml | 35 ++++ sysom_server/sysom_key_server/lib/README.md | 1 + sysom_server/sysom_key_server/main.py | 46 +++++ sysom_server/sysom_offline_push/alembic.ini | 102 +++++++++++ .../sysom_offline_push/alembic/README | 1 + .../sysom_offline_push/alembic/env.py | 115 ++++++++++++ .../sysom_offline_push/alembic/script.py.mako | 24 +++ .../alembic/versions/.gitkeep | 0 .../sysom_offline_push/app/__init__.py | 8 + sysom_server/sysom_offline_push/app/crud.py | 36 ++++ .../sysom_offline_push/app/database.py | 30 ++++ sysom_server/sysom_offline_push/app/models.py | 24 +++ .../app/offlinepush/addDecode.py | 33 ++++ .../app/offlinepush/libaddenv.so | Bin 0 -> 29976 bytes .../app/offlinepush/ossApi.py | 89 ++++++++++ .../app/offlinepush/sysom_offline_push.py | 168 ++++++++++++++++++ sysom_server/sysom_offline_push/app/query.py | 28 +++ .../sysom_offline_push/app/routers/health.py | 21 +++ .../sysom_offline_push/app/routers/person.py | 33 ++++ .../sysom_offline_push/app/schemas.py | 24 +++ .../sysom_offline_push/conf/common.py | 33 ++++ .../sysom_offline_push/conf/develop.py | 15 ++ .../sysom_offline_push/conf/gunicorn.py | 23 +++ .../sysom_offline_push/conf/product.py | 15 ++ .../sysom_offline_push/conf/settings.py | 19 ++ .../sysom_offline_push/conf/testing.py | 14 ++ sysom_server/sysom_offline_push/config.yml | 37 ++++ sysom_server/sysom_offline_push/lib/README.md | 1 + sysom_server/sysom_offline_push/libaddenv.so | Bin 0 -> 29976 bytes sysom_server/sysom_offline_push/main.py | 51 ++++++ 70 files changed, 2007 insertions(+) create mode 100644 script/server/sysom_key_server/clear.sh create mode 100644 script/server/sysom_key_server/db_migrate.sh create mode 100644 script/server/sysom_key_server/init.sh create mode 100644 script/server/sysom_key_server/install.sh create mode 100644 script/server/sysom_key_server/requirements.txt create mode 100644 script/server/sysom_key_server/start.sh create mode 100644 script/server/sysom_key_server/stop.sh create mode 100644 script/server/sysom_key_server/sysom-key_server.ini create mode 100644 script/server/sysom_key_server/uninstall.sh create mode 100644 script/server/sysom_offline_push/clear.sh create mode 100644 script/server/sysom_offline_push/db_migrate.sh create mode 100644 script/server/sysom_offline_push/init.sh create mode 100644 script/server/sysom_offline_push/install.sh create mode 100644 script/server/sysom_offline_push/requirements.txt create mode 100644 script/server/sysom_offline_push/start.sh create mode 100644 script/server/sysom_offline_push/stop.sh create mode 100644 script/server/sysom_offline_push/sysom-offline_push.ini create mode 100644 script/server/sysom_offline_push/uninstall.sh create mode 100644 sysom_server/sysom_key_server/alembic.ini create mode 100644 sysom_server/sysom_key_server/alembic/README create mode 100644 sysom_server/sysom_key_server/alembic/env.py create mode 100644 sysom_server/sysom_key_server/alembic/script.py.mako create mode 100644 sysom_server/sysom_key_server/alembic/versions/.gitkeep create mode 100644 sysom_server/sysom_key_server/app/__init__.py create mode 100644 sysom_server/sysom_key_server/app/crud.py create mode 100644 sysom_server/sysom_key_server/app/database.py create mode 100644 sysom_server/sysom_key_server/app/models.py create mode 100644 sysom_server/sysom_key_server/app/query.py create mode 100644 sysom_server/sysom_key_server/app/routers/aes.py create mode 100644 sysom_server/sysom_key_server/app/routers/health.py create mode 100644 sysom_server/sysom_key_server/app/routers/key_service.py create mode 100644 sysom_server/sysom_key_server/app/routers/person.py create mode 100644 sysom_server/sysom_key_server/app/schemas.py create mode 100644 sysom_server/sysom_key_server/conf/common.py create mode 100644 sysom_server/sysom_key_server/conf/develop.py create mode 100644 sysom_server/sysom_key_server/conf/gunicorn.py create mode 100644 sysom_server/sysom_key_server/conf/product.py create mode 100644 sysom_server/sysom_key_server/conf/settings.py create mode 100644 sysom_server/sysom_key_server/conf/testing.py create mode 100644 sysom_server/sysom_key_server/config.yml create mode 100644 sysom_server/sysom_key_server/lib/README.md create mode 100644 sysom_server/sysom_key_server/main.py create mode 100644 sysom_server/sysom_offline_push/alembic.ini create mode 100644 sysom_server/sysom_offline_push/alembic/README create mode 100644 sysom_server/sysom_offline_push/alembic/env.py create mode 100644 sysom_server/sysom_offline_push/alembic/script.py.mako create mode 100644 sysom_server/sysom_offline_push/alembic/versions/.gitkeep create mode 100644 sysom_server/sysom_offline_push/app/__init__.py create mode 100644 sysom_server/sysom_offline_push/app/crud.py create mode 100644 sysom_server/sysom_offline_push/app/database.py create mode 100644 sysom_server/sysom_offline_push/app/models.py create mode 100644 sysom_server/sysom_offline_push/app/offlinepush/addDecode.py create mode 100644 sysom_server/sysom_offline_push/app/offlinepush/libaddenv.so create mode 100644 sysom_server/sysom_offline_push/app/offlinepush/ossApi.py create mode 100644 sysom_server/sysom_offline_push/app/offlinepush/sysom_offline_push.py create mode 100644 sysom_server/sysom_offline_push/app/query.py create mode 100644 sysom_server/sysom_offline_push/app/routers/health.py create mode 100644 sysom_server/sysom_offline_push/app/routers/person.py create mode 100644 sysom_server/sysom_offline_push/app/schemas.py create mode 100644 sysom_server/sysom_offline_push/conf/common.py create mode 100644 sysom_server/sysom_offline_push/conf/develop.py create mode 100644 sysom_server/sysom_offline_push/conf/gunicorn.py create mode 100644 sysom_server/sysom_offline_push/conf/product.py create mode 100644 sysom_server/sysom_offline_push/conf/settings.py create mode 100644 sysom_server/sysom_offline_push/conf/testing.py create mode 100644 sysom_server/sysom_offline_push/config.yml create mode 100644 sysom_server/sysom_offline_push/lib/README.md create mode 100644 sysom_server/sysom_offline_push/libaddenv.so create mode 100644 sysom_server/sysom_offline_push/main.py diff --git a/deps/2_nginx/sysom.conf b/deps/2_nginx/sysom.conf index c0d01514..c504cf8d 100644 --- a/deps/2_nginx/sysom.conf +++ b/deps/2_nginx/sysom.conf @@ -179,6 +179,23 @@ server { # 7020 resvered for cluster_health + + # 7021 resvered for offline-push + location /api/v1/offline_push/ { + proxy_pass http://127.0.0.1:7021; + proxy_read_timeout 180; + proxy_redirect off; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 7022 resvered for key-server + location /api/v1/key_server/ { + proxy_pass http://127.0.0.1:7022; + proxy_read_timeout 180; + proxy_redirect off; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /api/ { proxy_pass http://127.0.0.1:7001; proxy_read_timeout 180s; diff --git a/script/server/sysom_key_server/clear.sh b/script/server/sysom_key_server/clear.sh new file mode 100644 index 00000000..87af8db1 --- /dev/null +++ b/script/server/sysom_key_server/clear.sh @@ -0,0 +1,14 @@ +#!/bin/bash +BaseDir=$(dirname $(readlink -f "$0")) +SERVICE_NAME=sysom-key_server + +clear_app() { + rm -rf /etc/supervisord.d/${SERVICE_NAME}.ini + ###use supervisorctl update to stop and clear services### + supervisorctl update +} + +# Stop first +bash -x $BaseDir/stop.sh + +clear_app diff --git a/script/server/sysom_key_server/db_migrate.sh b/script/server/sysom_key_server/db_migrate.sh new file mode 100644 index 00000000..f530e1f8 --- /dev/null +++ b/script/server/sysom_key_server/db_migrate.sh @@ -0,0 +1,18 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +db_migrate() { + pushd ${SERVICE_HOME} + alembic upgrade head + popd +} + +source_virtualenv +db_migrate \ No newline at end of file diff --git a/script/server/sysom_key_server/init.sh b/script/server/sysom_key_server/init.sh new file mode 100644 index 00000000..dda31e47 --- /dev/null +++ b/script/server/sysom_key_server/init.sh @@ -0,0 +1,25 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +BaseDir=$(dirname $(readlink -f "$0")) +SERVICE_NAME=sysom-key_server + +init_conf() { + cp ${SERVICE_NAME}.ini /etc/supervisord.d/ + ###change the install dir base on param $1### + sed -i "s;/usr/local/sysom;${APP_HOME};g" /etc/supervisord.d/${SERVICE_NAME}.ini + cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + sed -i "s/threads = 3/threads = $cpu_num/g" ${SERVICE_HOME}/conf/gunicorn.py +} + +init_app() { + init_conf + bash -x $BaseDir/db_migrate.sh + ###if supervisor service started, we need use "supervisorctl update" to start new conf#### + supervisorctl update +} + +init_app + +# Start +bash -x $BaseDir/start.sh diff --git a/script/server/sysom_key_server/install.sh b/script/server/sysom_key_server/install.sh new file mode 100644 index 00000000..826c0ddd --- /dev/null +++ b/script/server/sysom_key_server/install.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +SERVICE_SCRIPT_HOME=${MICROSERVICE_SCRIPT_HOME}/${SERVICE_SCRIPT_DIR} +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME +SERVICE_NAME=sysom-key_server + +if [ "$UID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +install_requirement() { + pushd ${SERVICE_SCRIPT_HOME} + pip install -r requirements.txt + popd +} + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +install_app() { + source_virtualenv + install_requirement +} + +install_app diff --git a/script/server/sysom_key_server/requirements.txt b/script/server/sysom_key_server/requirements.txt new file mode 100644 index 00000000..16ff7623 --- /dev/null +++ b/script/server/sysom_key_server/requirements.txt @@ -0,0 +1,20 @@ +clogger==0.0.1 +channel_job>=0.0.1 +cec_base>=0.0.1 +cec_redis>=0.0.1 +sysom_utils>=0.0.1 +alembic==1.7.7 +anyio==3.6.2 +asyncer==0.0.2 +asyncssh==2.12.0 +fastapi==0.83.0 +PyMySQL==1.0.2 +pyyaml==6.0 +pyyaml-include==1.3 +uvicorn==0.16.0 +gunicorn==20.1.0 +python-multipart==0.0.5 +###################################################################### +# Add your custom python requirements here +##################################################################### +pycryptodome==3.19.0 \ No newline at end of file diff --git a/script/server/sysom_key_server/start.sh b/script/server/sysom_key_server/start.sh new file mode 100644 index 00000000..cdf4009f --- /dev/null +++ b/script/server/sysom_key_server/start.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SERVICE_NAME=sysom-key_server + +is_start() { + status=`supervisorctl status ${SERVICE_NAME} | awk '{print $2}'` + result=`echo "RUNNING STARTING" | grep $status` + if [[ "$result" != "" ]] + then + return 1 + else + return 0 + fi +} + +start_app() { + is_start + if [[ $? == 0 ]]; then + supervisorctl start $SERVICE_NAME + is_start + if [[ $? == 0 ]]; then + echo "${SERVICE_NAME} service start fail, please check log" + exit 1 + else + echo "supervisorctl start ${SERVICE_NAME} success..." + fi + fi +} + +start_app diff --git a/script/server/sysom_key_server/stop.sh b/script/server/sysom_key_server/stop.sh new file mode 100644 index 00000000..5a423e82 --- /dev/null +++ b/script/server/sysom_key_server/stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash +SERVICE_NAME=sysom-key_server + +stop_app() { + supervisorctl stop $SERVICE_NAME +} + +stop_app diff --git a/script/server/sysom_key_server/sysom-key_server.ini b/script/server/sysom_key_server/sysom-key_server.ini new file mode 100644 index 00000000..dd5edf3a --- /dev/null +++ b/script/server/sysom_key_server/sysom-key_server.ini @@ -0,0 +1,9 @@ +[program:sysom-key_server] +directory=/usr/local/sysom/server/sysom_key_server +command=/usr/local/sysom/environment/virtualenv/bin/gunicorn -c ./conf/gunicorn.py main:app +startsecs=3 +autostart=true +autorestart=true +environment=PATH=/usr/local/sysom/virtualenv/bin:%(ENV_PATH)s +stderr_logfile=/var/log/sysom/sysom-key_server-error.log +stdout_logfile=/var/log/sysom/sysom-key_server.log diff --git a/script/server/sysom_key_server/uninstall.sh b/script/server/sysom_key_server/uninstall.sh new file mode 100644 index 00000000..0aac04c0 --- /dev/null +++ b/script/server/sysom_key_server/uninstall.sh @@ -0,0 +1,9 @@ +#!/bin/bash +BaseDir=$(dirname $(readlink -f "$0")) + +uninstall_app() { + # do nothing + echo "" +} + +uninstall_app \ No newline at end of file diff --git a/script/server/sysom_offline_push/clear.sh b/script/server/sysom_offline_push/clear.sh new file mode 100644 index 00000000..5fbc000d --- /dev/null +++ b/script/server/sysom_offline_push/clear.sh @@ -0,0 +1,14 @@ +#!/bin/bash +BaseDir=$(dirname $(readlink -f "$0")) +SERVICE_NAME=sysom-offline_push + +clear_app() { + rm -rf /etc/supervisord.d/${SERVICE_NAME}.ini + ###use supervisorctl update to stop and clear services### + supervisorctl update +} + +# Stop first +bash -x $BaseDir/stop.sh + +clear_app diff --git a/script/server/sysom_offline_push/db_migrate.sh b/script/server/sysom_offline_push/db_migrate.sh new file mode 100644 index 00000000..f530e1f8 --- /dev/null +++ b/script/server/sysom_offline_push/db_migrate.sh @@ -0,0 +1,18 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +db_migrate() { + pushd ${SERVICE_HOME} + alembic upgrade head + popd +} + +source_virtualenv +db_migrate \ No newline at end of file diff --git a/script/server/sysom_offline_push/init.sh b/script/server/sysom_offline_push/init.sh new file mode 100644 index 00000000..c2a8cea5 --- /dev/null +++ b/script/server/sysom_offline_push/init.sh @@ -0,0 +1,25 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +BaseDir=$(dirname $(readlink -f "$0")) +SERVICE_NAME=sysom-offline_push + +init_conf() { + cp ${SERVICE_NAME}.ini /etc/supervisord.d/ + ###change the install dir base on param $1### + sed -i "s;/usr/local/sysom;${APP_HOME};g" /etc/supervisord.d/${SERVICE_NAME}.ini + cpu_num=$(cat /proc/cpuinfo | grep processor | wc -l) + sed -i "s/threads = 3/threads = $cpu_num/g" ${SERVICE_HOME}/conf/gunicorn.py +} + +init_app() { + init_conf + bash -x $BaseDir/db_migrate.sh + ###if supervisor service started, we need use "supervisorctl update" to start new conf#### + supervisorctl update +} + +init_app + +# Start +bash -x $BaseDir/start.sh diff --git a/script/server/sysom_offline_push/install.sh b/script/server/sysom_offline_push/install.sh new file mode 100644 index 00000000..77f16e8c --- /dev/null +++ b/script/server/sysom_offline_push/install.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SERVICE_SCRIPT_DIR=$(basename $(dirname $0)) +SERVICE_HOME=${MICROSERVICE_HOME}/${SERVICE_SCRIPT_DIR} +SERVICE_SCRIPT_HOME=${MICROSERVICE_SCRIPT_HOME}/${SERVICE_SCRIPT_DIR} +VIRTUALENV_HOME=$GLOBAL_VIRTUALENV_HOME +SERVICE_NAME=sysom-offline_push + +if [ "$UID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +install_requirement() { + pushd ${SERVICE_SCRIPT_HOME} + pip install -r requirements.txt + popd +} + +source_virtualenv() { + echo "INFO: activate virtualenv..." + source ${VIRTUALENV_HOME}/bin/activate || exit 1 +} + +install_app() { + source_virtualenv + install_requirement +} + +install_app diff --git a/script/server/sysom_offline_push/requirements.txt b/script/server/sysom_offline_push/requirements.txt new file mode 100644 index 00000000..ae727ce7 --- /dev/null +++ b/script/server/sysom_offline_push/requirements.txt @@ -0,0 +1,21 @@ +clogger==0.0.1 +channel_job>=0.0.1 +cec_base>=0.0.1 +cec_redis>=0.0.1 +sysom_utils>=0.0.1 +alembic==1.7.7 +anyio==3.6.2 +asyncer==0.0.2 +asyncssh==2.12.0 +fastapi==0.83.0 +PyMySQL==1.0.2 +pyyaml==6.0 +pyyaml-include==1.3 +uvicorn==0.16.0 +gunicorn==20.1.0 +python-multipart==0.0.5 +###################################################################### +# Add your custom python requirements here +###################################################################### +pycryptodome==3.19.0 +aliyun-log-python-sdk==0.8.12 \ No newline at end of file diff --git a/script/server/sysom_offline_push/start.sh b/script/server/sysom_offline_push/start.sh new file mode 100644 index 00000000..4aa7da55 --- /dev/null +++ b/script/server/sysom_offline_push/start.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SERVICE_NAME=sysom-offline_push + +is_start() { + status=`supervisorctl status ${SERVICE_NAME} | awk '{print $2}'` + result=`echo "RUNNING STARTING" | grep $status` + if [[ "$result" != "" ]] + then + return 1 + else + return 0 + fi +} + +start_app() { + is_start + if [[ $? == 0 ]]; then + supervisorctl start $SERVICE_NAME + is_start + if [[ $? == 0 ]]; then + echo "${SERVICE_NAME} service start fail, please check log" + exit 1 + else + echo "supervisorctl start ${SERVICE_NAME} success..." + fi + fi +} + +start_app diff --git a/script/server/sysom_offline_push/stop.sh b/script/server/sysom_offline_push/stop.sh new file mode 100644 index 00000000..a71c1d8b --- /dev/null +++ b/script/server/sysom_offline_push/stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash +SERVICE_NAME=sysom-offline_push + +stop_app() { + supervisorctl stop $SERVICE_NAME +} + +stop_app diff --git a/script/server/sysom_offline_push/sysom-offline_push.ini b/script/server/sysom_offline_push/sysom-offline_push.ini new file mode 100644 index 00000000..d11932c0 --- /dev/null +++ b/script/server/sysom_offline_push/sysom-offline_push.ini @@ -0,0 +1,9 @@ +[program:sysom-offline_push] +directory=/usr/local/sysom/server/sysom_offline_push +command=/usr/local/sysom/environment/virtualenv/bin/gunicorn -c ./conf/gunicorn.py main:app +startsecs=3 +autostart=true +autorestart=true +environment=PATH=/usr/local/sysom/virtualenv/bin:%(ENV_PATH)s +stderr_logfile=/var/log/sysom/sysom-offline_push-error.log +stdout_logfile=/var/log/sysom/sysom-offline_push.log diff --git a/script/server/sysom_offline_push/uninstall.sh b/script/server/sysom_offline_push/uninstall.sh new file mode 100644 index 00000000..0aac04c0 --- /dev/null +++ b/script/server/sysom_offline_push/uninstall.sh @@ -0,0 +1,9 @@ +#!/bin/bash +BaseDir=$(dirname $(readlink -f "$0")) + +uninstall_app() { + # do nothing + echo "" +} + +uninstall_app \ No newline at end of file diff --git a/sysom_server/sysom_key_server/alembic.ini b/sysom_server/sysom_key_server/alembic.ini new file mode 100644 index 00000000..f6ab9feb --- /dev/null +++ b/sysom_server/sysom_key_server/alembic.ini @@ -0,0 +1,102 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = "" + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/sysom_server/sysom_key_server/alembic/README b/sysom_server/sysom_key_server/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/sysom_server/sysom_key_server/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/sysom_server/sysom_key_server/alembic/env.py b/sysom_server/sysom_key_server/alembic/env.py new file mode 100644 index 00000000..ad3427d1 --- /dev/null +++ b/sysom_server/sysom_key_server/alembic/env.py @@ -0,0 +1,115 @@ +import inspect +import app.models as models +from logging.config import fileConfig +from sqlalchemy import engine_from_config, Table +from sqlalchemy import pool +from app.models import Base +from alembic import context +from conf.settings import YAML_CONFIG, SQLALCHEMY_DATABASE_URL + +################################################################## +# Load yaml config first +################################################################## +mysql_config = YAML_CONFIG.get_server_config().db.mysql + +################################################################## +# Scan models +################################################################## +service_tables = [] +for name, data in inspect.getmembers(models): + if inspect.isclass(data): + if data.__module__ != "app.models": + continue + if "__tablename__" in data.__dict__: + service_tables.append(data.__dict__["__tablename__"]) + elif "__table__" in data.__dict__: + service_tables.append(data.__dict__["__table__"]) + elif isinstance(data, Table): + service_tables.append(name) + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# Update mysql config according config.yml +config.set_main_option( + "sqlalchemy.url", + SQLALCHEMY_DATABASE_URL +) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +def include_object(object, name, type_, reflected, compare_to): + if type_ == "table" and name not in service_tables: + return False + return True + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + include_object=include_object, + version_table="key_server_version", + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata, + include_object=include_object, + version_table="key_server_version" + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/sysom_server/sysom_key_server/alembic/script.py.mako b/sysom_server/sysom_key_server/alembic/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/sysom_server/sysom_key_server/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/sysom_server/sysom_key_server/alembic/versions/.gitkeep b/sysom_server/sysom_key_server/alembic/versions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sysom_server/sysom_key_server/app/__init__.py b/sysom_server/sysom_key_server/app/__init__.py new file mode 100644 index 00000000..fa55a3b9 --- /dev/null +++ b/sysom_server/sysom_key_server/app/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File __init__.py +Description: +""" \ No newline at end of file diff --git a/sysom_server/sysom_key_server/app/crud.py b/sysom_server/sysom_key_server/app/crud.py new file mode 100644 index 00000000..1bdc78f1 --- /dev/null +++ b/sysom_server/sysom_key_server/app/crud.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File crud.py +Description: +""" +from typing import Optional, List +from sqlalchemy.orm import Session +from app import models, schemas, query + +################################################################################################ +# Define database crud here +################################################################################################ + +# def get_person_by_name(db: Session, name: str) -> Optional[models.Person]: +# return db.query(models.Person).filter(models.Person.name == name).first() + +# def create_person(db: Session, person: schemas.Person) -> models.Person: +# person = models.Person(**person.dict()) +# db.add(person) +# db.commit() +# db.refresh(person) +# return person + +# def del_person_by_id(db: Session, person_id: int): +# person = db.get(models.Person, person_id) +# db.delete(person) +# db.commit() + +# def get_person_list(db: Session, query_params: query.PersonQueryParams) -> List[models.Person]: +# return ( +# query_params.get_query_exp(db) +# .all() +# ) diff --git a/sysom_server/sysom_key_server/app/database.py b/sysom_server/sysom_key_server/app/database.py new file mode 100644 index 00000000..ea519ec5 --- /dev/null +++ b/sysom_server/sysom_key_server/app/database.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File database.py +Description: +""" +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from conf.settings import SQLALCHEMY_DATABASE_URL +from sysom_utils import FastApiResponseHelper + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={} +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +Base = declarative_base() + +FastApiResponseHelper.bind_base_class(Base) \ No newline at end of file diff --git a/sysom_server/sysom_key_server/app/models.py b/sysom_server/sysom_key_server/app/models.py new file mode 100644 index 00000000..e61ee5f1 --- /dev/null +++ b/sysom_server/sysom_key_server/app/models.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File models.py +Description: +""" +from sqlalchemy import Column, Integer, String, DateTime +from sqlalchemy.sql import func +from app.database import Base + + +########################################################################### +# Define databse model here +########################################################################### + +# @reference https://fastapi.tiangolo.com/zh/tutorial/sql-databases/ +# class Person(Base): +# __tablename__ = "sys_person" +# id = Column(Integer, primary_key=True) +# name = Column(String(254), unique=True) +# age = Column(Integer) +# created_at = Column(DateTime(timezone=True), server_default=func.now()) \ No newline at end of file diff --git a/sysom_server/sysom_key_server/app/query.py b/sysom_server/sysom_key_server/app/query.py new file mode 100644 index 00000000..fcbcd089 --- /dev/null +++ b/sysom_server/sysom_key_server/app/query.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- # +""" +Time 2023/09/19 15:41 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File schemas.py +Description: +""" +from typing import Optional +from app import models +from sysom_utils import BaseQueryParams + + +# class PersonQueryParams(BaseQueryParams): + +# # 1. 指定要查询的模型 +# __modelclass__ = models.Person + +# # 2. 定义排序字段 +# sort: str = "-created_at" + +# # 3. 定义支持用于过滤的参数 +# name: Optional[str] = None +# age: Optional[str] = None + +# # 4. 指定哪些字段是枚举类型,并且指明对应的枚举类 +# __enum_fields__ = { +# } \ No newline at end of file diff --git a/sysom_server/sysom_key_server/app/routers/aes.py b/sysom_server/sysom_key_server/app/routers/aes.py new file mode 100644 index 00000000..1909692e --- /dev/null +++ b/sysom_server/sysom_key_server/app/routers/aes.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +''' +@File : aes.py +@Time : 2023/12/19 13:55:26 +@Author : pengchen +@e-mail : wb-cp908446@alibaba-inc.com +@Version : 1.0 +@Desc : +''' +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad, unpad + +def convert_to_bytes(data): + data_bytes = data.encode() # 将字符串转换为bytes类型 + if len(data_bytes) % 16 != 0: + padding_length = 16 - (len(data_bytes) % 16) + data_bytes += b'\x00' * padding_length # 使用b'\x00'进行填充 + return data_bytes + +def aes_encrypt(key, message): + # 创建AES加密器 + cipher = AES.new(key, AES.MODE_ECB) + # 进行填充 + padded_data = pad(message, AES.block_size) + # 进行加密 + ciphertext = cipher.encrypt(padded_data) + return ciphertext + +def aes_decrypt(key, ciphertext): + # 创建AES解密器 + cipher = AES.new(key, AES.MODE_ECB) + # 进行解密 + decrypted_data = cipher.decrypt(ciphertext) + # 进行去填充 + unpadded_data = unpad(decrypted_data, AES.block_size) + return unpadded_data + + diff --git a/sysom_server/sysom_key_server/app/routers/health.py b/sysom_server/sysom_key_server/app/routers/health.py new file mode 100644 index 00000000..cd614ff7 --- /dev/null +++ b/sysom_server/sysom_key_server/app/routers/health.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File health.py +Description: +""" +from fastapi import APIRouter + + +router = APIRouter() + + +@router.get("/check") +async def get_channel_config(): + return { + "code": 0, + "err_msg": "", + "data": "" + } diff --git a/sysom_server/sysom_key_server/app/routers/key_service.py b/sysom_server/sysom_key_server/app/routers/key_service.py new file mode 100644 index 00000000..c842814b --- /dev/null +++ b/sysom_server/sysom_key_server/app/routers/key_service.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/17 16:27 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File health.py +Description: +""" +import os +import time +import base64 +from app.routers.aes import aes_encrypt, convert_to_bytes +from fastapi import APIRouter +from conf.common import YAML_CONFIG + +addtion = YAML_CONFIG.get_service_config().get("ADDTION", None) + +router = APIRouter() + +@router.get("/key") +async def get_key_server_handler(): + original_bt = str(int(time.time())) + original_bt = original_bt.encode() + + #时间加密 + timekey = aes_encrypt(convert_to_bytes("pyroscope"), original_bt) + # 进行时间二次加密 + ciphertimetwo = aes_encrypt(convert_to_bytes("treelib"), timekey) + ciphertimetwo = (base64.b64encode(ciphertimetwo)).decode() + + # 原始数据 + addtion_byte = addtion.encode() + # 数据进行加密 + ciphermsgtext = aes_encrypt(timekey, addtion_byte) + encypt = (base64.b64encode(ciphermsgtext)).decode() + original_msg = ciphertimetwo + encypt + return original_msg diff --git a/sysom_server/sysom_key_server/app/routers/person.py b/sysom_server/sysom_key_server/app/routers/person.py new file mode 100644 index 00000000..ebf6e39c --- /dev/null +++ b/sysom_server/sysom_key_server/app/routers/person.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File health.py +Description: +""" +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from app.query import PersonQueryParams +from app.database import get_db +from app.crud import get_person_list, get_person_by_name +from app.schemas import Person +from sysom_utils import StandardListResponse, StandardResponse + + +router = APIRouter() + + +@router.get("/get") +async def get_specific_person( + person_name: str, db: Session = Depends(get_db) +): + person = get_person_by_name(db, person_name) + return StandardResponse(person, Person) + +@router.get("/list") +async def get_persons( + query_params: PersonQueryParams = Depends(), db: Session = Depends(get_db) +): + person_list = get_person_list(db, query_params) + return StandardListResponse(person_list, Person) diff --git a/sysom_server/sysom_key_server/app/schemas.py b/sysom_server/sysom_key_server/app/schemas.py new file mode 100644 index 00000000..4e8bdef4 --- /dev/null +++ b/sysom_server/sysom_key_server/app/schemas.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File schemas.py +Description: +""" +from pydantic import BaseModel +from datetime import datetime + +########################################################################### +# Define schemas here +########################################################################### + +# @reference https://fastapi.tiangolo.com/zh/tutorial/response-model/ +# class Person(BaseModel): +# id: int +# name: str +# age: int +# created_at: datetime + +# class Config: +# orm_mode = True \ No newline at end of file diff --git a/sysom_server/sysom_key_server/conf/common.py b/sysom_server/sysom_key_server/conf/common.py new file mode 100644 index 00000000..a176ddb0 --- /dev/null +++ b/sysom_server/sysom_key_server/conf/common.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File common.py +Description: +""" +from pathlib import Path +from sysom_utils import ConfigParser, SysomFramework + +BASE_DIR = Path(__file__).resolve().parent.parent + +################################################################## +# Load yaml config first +################################################################## +YAML_GLOBAL_CONFIG_PATH = f"{BASE_DIR.parent.parent}/conf/config.yml" +YAML_SERVICE_CONFIG_PATH = f"{BASE_DIR}/config.yml" + +YAML_CONFIG = ConfigParser(YAML_GLOBAL_CONFIG_PATH, YAML_SERVICE_CONFIG_PATH) + +mysql_config = YAML_CONFIG.get_server_config().db.mysql +service_config = YAML_CONFIG.get_service_config() + +SysomFramework.init(YAML_CONFIG) + +################################################################## +# fastapi config +################################################################## +SQLALCHEMY_DATABASE_URL = ( + f"{mysql_config.dialect}+{mysql_config.engine}://{mysql_config.user}:{mysql_config.password}@" + f"{mysql_config.host}:{mysql_config.port}/{mysql_config.database}" +) \ No newline at end of file diff --git a/sysom_server/sysom_key_server/conf/develop.py b/sysom_server/sysom_key_server/conf/develop.py new file mode 100644 index 00000000..af922d74 --- /dev/null +++ b/sysom_server/sysom_key_server/conf/develop.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File develoop.py +Description: +""" +from .common import * + +''' +开发环境配置项 +''' + +DEBUG = True \ No newline at end of file diff --git a/sysom_server/sysom_key_server/conf/gunicorn.py b/sysom_server/sysom_key_server/conf/gunicorn.py new file mode 100644 index 00000000..a49f6994 --- /dev/null +++ b/sysom_server/sysom_key_server/conf/gunicorn.py @@ -0,0 +1,23 @@ +''' +Channel Service Gunicorn Settings +''' +from conf.common import YAML_CONFIG + +bind = YAML_CONFIG.get_service_config().get("bind", "127.0.0.1") +port = YAML_CONFIG.get_service_config().get("port", "80") + +workers = 2 # 指定工作进程数 + +threads = 3 + +bind = f'{bind}:{port}' + +worker_class = 'uvicorn.workers.UvicornWorker' # 工作模式线程, 默认为sync模式 + +max_requests = 2000 # 设置最大并发数量为2000 (每个worker处理请求的工作线程) + +accesslog = '/var/log/sysom/sysom-key_server-access.log' + +loglevel = 'error' + +proc_name = 'sysom_key_server_service' diff --git a/sysom_server/sysom_key_server/conf/product.py b/sysom_server/sysom_key_server/conf/product.py new file mode 100644 index 00000000..efce0452 --- /dev/null +++ b/sysom_server/sysom_key_server/conf/product.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File product.py +Description: +""" +from .common import * + +''' +生产环境配置项 +''' + +DEBUG = False diff --git a/sysom_server/sysom_key_server/conf/settings.py b/sysom_server/sysom_key_server/conf/settings.py new file mode 100644 index 00000000..40550d5e --- /dev/null +++ b/sysom_server/sysom_key_server/conf/settings.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File settings.py +Description: +""" +import os + +env = os.environ.get("env", "product") + + +if env == "develop": + from .develop import * +elif env == "testing": + from .testing import * +elif env == "product": + from .product import * \ No newline at end of file diff --git a/sysom_server/sysom_key_server/conf/testing.py b/sysom_server/sysom_key_server/conf/testing.py new file mode 100644 index 00000000..4bfc178d --- /dev/null +++ b/sysom_server/sysom_key_server/conf/testing.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File testing.py +Description: +""" +from .common import * + +''' +测试环境配置项 +''' +DEBUG = True diff --git a/sysom_server/sysom_key_server/config.yml b/sysom_server/sysom_key_server/config.yml new file mode 100644 index 00000000..247c64cd --- /dev/null +++ b/sysom_server/sysom_key_server/config.yml @@ -0,0 +1,35 @@ +vars: + SERVICE_NAME: &SERVICE_NAME sysom_key_server + SERVICE_CONSUMER_GROUP: + !concat &SERVICE_CONSUMER_GROUP [*SERVICE_NAME, "_consumer_group"] + +sysom_server: + cec: + consumer_group: *SERVICE_CONSUMER_GROUP + +sysom_service: + service_name: *SERVICE_NAME + service_dir: *SERVICE_NAME + protocol: http + host: 127.0.0.1 + bind: 127.0.0.1 + port: 7022 + framework: + gcache: + protocol: redis + node_dispatch: + cmg: + tags: + - key_server + - FastApi + # Metadata of service + metadata: + check: + type: http + url: "/api/v1/key_server/health/check" + interval: 10 + timeout: 10 + deregister: 25 + header: + tls_skip_verify: false + diff --git a/sysom_server/sysom_key_server/lib/README.md b/sysom_server/sysom_key_server/lib/README.md new file mode 100644 index 00000000..3ec74424 --- /dev/null +++ b/sysom_server/sysom_key_server/lib/README.md @@ -0,0 +1 @@ +The current directory holds the public libraries or utils needed for microservices \ No newline at end of file diff --git a/sysom_server/sysom_key_server/main.py b/sysom_server/sysom_key_server/main.py new file mode 100644 index 00000000..ce85cc7d --- /dev/null +++ b/sysom_server/sysom_key_server/main.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/17 16:27 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File ssh.py +Description: +""" +from clogger import logger +from fastapi import FastAPI +from app.routers import health, key_service +from conf.settings import YAML_CONFIG +from sysom_utils import CmgPlugin, SysomFramework + + +app = FastAPI() + +app.include_router(health.router, prefix="/api/v1/key_server/health") +# app.include_router(health.router, prefix="/api/v1/key_server/person") +app.include_router(key_service.router, prefix="/api/v1/key_server") + +############################################################################# +# Write your API interface here, or add to app/routes +############################################################################# + + +def init_framwork(): + SysomFramework\ + .init(YAML_CONFIG) \ + .load_plugin_cls(CmgPlugin) \ + .start() + logger.info("SysomFramework init finished!") + + +@app.on_event("startup") +async def on_start(): + init_framwork() + + ############################################################################# + # Perform some microservice initialization operations over here + ############################################################################# + + +@app.on_event("shutdown") +async def on_shutdown(): + pass \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/alembic.ini b/sysom_server/sysom_offline_push/alembic.ini new file mode 100644 index 00000000..f6ab9feb --- /dev/null +++ b/sysom_server/sysom_offline_push/alembic.ini @@ -0,0 +1,102 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = "" + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/sysom_server/sysom_offline_push/alembic/README b/sysom_server/sysom_offline_push/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/sysom_server/sysom_offline_push/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/alembic/env.py b/sysom_server/sysom_offline_push/alembic/env.py new file mode 100644 index 00000000..ba0ab30f --- /dev/null +++ b/sysom_server/sysom_offline_push/alembic/env.py @@ -0,0 +1,115 @@ +import inspect +import app.models as models +from logging.config import fileConfig +from sqlalchemy import engine_from_config, Table +from sqlalchemy import pool +from app.models import Base +from alembic import context +from conf.settings import YAML_CONFIG, SQLALCHEMY_DATABASE_URL + +################################################################## +# Load yaml config first +################################################################## +mysql_config = YAML_CONFIG.get_server_config().db.mysql + +################################################################## +# Scan models +################################################################## +service_tables = [] +for name, data in inspect.getmembers(models): + if inspect.isclass(data): + if data.__module__ != "app.models": + continue + if "__tablename__" in data.__dict__: + service_tables.append(data.__dict__["__tablename__"]) + elif "__table__" in data.__dict__: + service_tables.append(data.__dict__["__table__"]) + elif isinstance(data, Table): + service_tables.append(name) + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# Update mysql config according config.yml +config.set_main_option( + "sqlalchemy.url", + SQLALCHEMY_DATABASE_URL +) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +def include_object(object, name, type_, reflected, compare_to): + if type_ == "table" and name not in service_tables: + return False + return True + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + include_object=include_object, + version_table="offline_push_version", + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata, + include_object=include_object, + version_table="offline_push_version" + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/sysom_server/sysom_offline_push/alembic/script.py.mako b/sysom_server/sysom_offline_push/alembic/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/sysom_server/sysom_offline_push/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/sysom_server/sysom_offline_push/alembic/versions/.gitkeep b/sysom_server/sysom_offline_push/alembic/versions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sysom_server/sysom_offline_push/app/__init__.py b/sysom_server/sysom_offline_push/app/__init__.py new file mode 100644 index 00000000..fa55a3b9 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File __init__.py +Description: +""" \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/app/crud.py b/sysom_server/sysom_offline_push/app/crud.py new file mode 100644 index 00000000..1bdc78f1 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/crud.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File crud.py +Description: +""" +from typing import Optional, List +from sqlalchemy.orm import Session +from app import models, schemas, query + +################################################################################################ +# Define database crud here +################################################################################################ + +# def get_person_by_name(db: Session, name: str) -> Optional[models.Person]: +# return db.query(models.Person).filter(models.Person.name == name).first() + +# def create_person(db: Session, person: schemas.Person) -> models.Person: +# person = models.Person(**person.dict()) +# db.add(person) +# db.commit() +# db.refresh(person) +# return person + +# def del_person_by_id(db: Session, person_id: int): +# person = db.get(models.Person, person_id) +# db.delete(person) +# db.commit() + +# def get_person_list(db: Session, query_params: query.PersonQueryParams) -> List[models.Person]: +# return ( +# query_params.get_query_exp(db) +# .all() +# ) diff --git a/sysom_server/sysom_offline_push/app/database.py b/sysom_server/sysom_offline_push/app/database.py new file mode 100644 index 00000000..ea519ec5 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/database.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File database.py +Description: +""" +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from conf.settings import SQLALCHEMY_DATABASE_URL +from sysom_utils import FastApiResponseHelper + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={} +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +Base = declarative_base() + +FastApiResponseHelper.bind_base_class(Base) \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/app/models.py b/sysom_server/sysom_offline_push/app/models.py new file mode 100644 index 00000000..e61ee5f1 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/models.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File models.py +Description: +""" +from sqlalchemy import Column, Integer, String, DateTime +from sqlalchemy.sql import func +from app.database import Base + + +########################################################################### +# Define databse model here +########################################################################### + +# @reference https://fastapi.tiangolo.com/zh/tutorial/sql-databases/ +# class Person(Base): +# __tablename__ = "sys_person" +# id = Column(Integer, primary_key=True) +# name = Column(String(254), unique=True) +# age = Column(Integer) +# created_at = Column(DateTime(timezone=True), server_default=func.now()) \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/app/offlinepush/addDecode.py b/sysom_server/sysom_offline_push/app/offlinepush/addDecode.py new file mode 100644 index 00000000..d6b3b64f --- /dev/null +++ b/sysom_server/sysom_offline_push/app/offlinepush/addDecode.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +------------------------------------------------- + File Name: addDecode + Description : + Author : liaozhaoyan + date: 2023/11/6 +------------------------------------------------- + Change Activity: + 2023/11/6: +------------------------------------------------- +""" +__author__ = 'liaozhaoyan' + +import os +from ctypes import cdll, c_char_p, c_int, create_string_buffer + +lib = cdll.LoadLibrary('./libaddenv.so') + +def decode_lib(s): + add_decode = lib.add_decode + add_decode.argtypes = [c_char_p, c_char_p, c_char_p] + add_decode.restype = c_int + + e1 = create_string_buffer(100) + e2 = create_string_buffer(100) + + add_decode(s.encode(), e1, e2) + return e1.value.decode(), e2.value.decode() + + +if __name__ == "__main__": + pass diff --git a/sysom_server/sysom_offline_push/app/offlinepush/libaddenv.so b/sysom_server/sysom_offline_push/app/offlinepush/libaddenv.so new file mode 100644 index 0000000000000000000000000000000000000000..dac2fabf19b8762eb4acd35f79eb970414f54927 GIT binary patch literal 29976 zcmeHweOy&l_V>B>f}){dQBs-SQVUB7e9IIQU#=Y;DOBjq?23Rw!&`76G&PGTZr3|8 zr47|gIlb4+r2VFxvNX^FdYQUemN`>HN_7#Ms7Pssyw7*-w|mY7`T)Pr^F04__#F1y zYpuQ3+I#KybMD!RH}m zAj5|H@+;D{72n*D`&$0OKj%GsFgbC>wO>zq>csn`Vg&9XxJk@l+|4!TMOa6Sxb(tt z)|B6eO@5kaLvato9fkW;+$5K-({Z1HI~MnuxX;2(*C-yKK4{u#+&ATK8nXVLPd6R= z;G5wS%6459HugaC%ieE?+edGG@?^@9OFy~kno+Mlmh=28NAKP6h4a2i^Bo7MoBP8jjZa=?z2>q7fB(;vzX(;`0=;?nDC2)Uw*@=C_f=*w~%v>z?TTT_d<^N zD>W>-oX)lRg$2$MS4OeR>C~KSatm@b=c2R}XI6G`_S)PMS9Wn)%KW^-g6y=6)p^;R z)-%PKS)M@xGV*fQgK=@0bD5AZKQE)CB)de*$jXAM;@pC@8iKs+0&Vs5snC>Jn3b*N zXXj@Ytz$d_GuN@)342`5Ojo%xw@jn>{CV>!LGijGmveRLnl;(QT470vl#!F4k?AbS z$w(x@xdmk-pxKN|i3c6IwtVvtuEH@cL%9+s` zgG!6CGF;glW<|q6S8+x`$(q9Ad@V0`b!L1?Vf=K>nGMzqcw{y5!yE!sL6&y;l10e# zyOe{km`2S4LM$H{q6l2W^CHy0#|xgF1m25wgYjx z6?;NeJhU1PxW+QUh!4EQmM8{>unFUM~p7a8!zaxORE z!wqsa8}M?RCvuemZ!BlMqAf!Agjc59HNCRd74Cl<%zDM^5&OB&z_fco3XI>1|50O- z0Z}}Mh%HS4T;uPixP{_A7Qdb1RMaiCEWV!NR0J(MS-hCyl$Dn4EPf-!DGM#tEWVoJ zRCQW5v-ousr)tnr#Nx{+PFZMK#o~)8PDS66%Hs1VPF1EQnZ;*PoLWmu0*ge}{aT?WIG{kjpcmfc*cQ)bE^$DH2`wP9IbNJ2HmHSa}Nol;`zFQe+ zqVfU%kA(8h{tkiUuj{nw?tOaQu{pZMr@!A>$`YSGh9y4W`fjD}t`8ic^qXhD45U_i zfnG6tFhS|i=Zb{9q08)=eXU zZ8CF@N zxqW(#?oAEo9>*cwvJYrIvk?)$ZfT6#rBzug99m^6)UPtxrRm=pIe7Vx3-6}LqfF_>qj&MpskqW;} z&-CfuN-jgUksO^{Pzw=^A7$|q!+Rs61?z6A;qapFe$QVc`N&SK!O;nl)*9n+bm}!x zyMRlYS!!*KtZ;Oq&^l*1x;8ZPuv8dUO|m?dtjH>At@Ya6V9ZjlrHNR*wuEHDT*2#= z<}x!zFf*VN=Gr8S#aB<6?)0R0F~4T|NCa4Pi%vPU)Pa*ZiRby9u0Y@=vj0u#)*|wc zd(YD+nOnEBU|S6Zp3{$09rFzPiUM!tPDW#;KOSQAiao@wxrPFw1RC;7qFerfe^ARs zHuxL=OO>MYwvC;eL6G%g7cqIgMdVw~yPL?uH!hy0Vq>SaPLyILsK{JJQxrP3!K`w% zx?)K;;v zOi%p*%rBfT;y)l-JeiP z^Mju`M!)TKvUr7P(iLrX>Lv7wjef0kFym;wUZ-vViB4lSb$A>dsBM+CQTo2RPOBST zho#oyLlXx9K5ZQ(9KbpcA3Beu*`cLHu4_P z;bS9JTU}>#l~v}^U{TU+*SS;|DL<5T#U4TIofWkfQIewj44UT}Ez^-WkxnGCA`yHP zhQ8efrL!FEQCrr+i>UpODEd%Q(}e$89%Xf{tF8&ng%<;juoOjKO!D~{%W-6vnTs$z zGKPx7NYXQ-t(!!fnUQK8hG<*VooT4;u$xWaUSk!mD;%gfWGxLUoje0ng{vG*q8x?2 z9QipzD%&{n2tz76IPxfR%vwxTGT$3sG?QycY-D+5 zE_1YlAsnNfDiYokVAhl(3J30tAWM-9HLcz3QC9KDo#+Szp>29y2xBKn`x*WEJoG9AUp=z zNl;2xVx#;0L~8Lc^rNZ}X-ldtretK3LHfsNxjjiwqhvja50$s1+8QFCL@vy$ZDTCeSA9d(@p5A~o-W=B6#j&IS1!tJOBD2rZ9dj{v23$dlft zJl<+Vz{6FJV`dC$dy5%^T8k>`Xoc_Ap(jJ@1XQS?Yz3`gkmDH0sS3F;h@7R6i3++v zLFaJB7NkBk2o6^CpD=`Q-QO#yg+UHK(tH*KKTz~HgUG!K`GSI;RnT_MNO{}_(7j(| zxJq$s0+e`25egL{TR|&`g;G)pT!F}B$FaHY*9Bs>;+vwN34j(PHnBvM@iP@A3eaRn z3r|j@lUO$K>~|>W7$BLP%HXh~_!LwRXtKi()7cox>jG<3%%e{+Mh5;~rMWkVyh|ZB zDyT$3xtx*mdxJuzDQK~Rk~t$~WST-=6hzt;@(cwg?w2-I{-~~v$;V3-@^S^uR?rmANI8pB$k9RMX$m<+L4i)0 z@gEq(GImTMn*hO`s(JbprCvd=0#eO$heAK0poajV$tLcf>;5O|7-ggiC?=F5wbgw=|0je@RVEV5QmUXy?^x%DE&e6E5<0_w@FG`dh-K5;^p!`BRA$Ri4QNI~x^ z=ylFW4t_ZZKBwr9E9gN5RdYtlTxAekujoZVWVS-CP|#8ZUCtR<{uOevg5nf3nln-! zPXlOlrcE*ZcwFZG8$imLM-=)HAXBf~ph$ZZ^jAQ#@@BrKt~IQf;env|$<9W{dhw8vKrj46!TbULTyp{x7D^ zUtaXTo;nW;&Q`@-nazMD+|KFK9i^*}w)R|#no0wP|rp{4n zHf*QaFs+!VVA+z0=9upK*J!%r|I>GY00#^PoucB$he{Q)e) z&?;#W)5KON?swt;Y)GN`S?fzi1@!dAzZfLbg0V+i80#2r3oBtAQZq2mlX^env!f8b zUw_Li)L`MMlC${-;PS~9rPi>%WLpz*F?(1|Br%KZjcG2X?n08GAX_h16K2akzN$k3 zinV$vncbvY+OhvZYiu!fjVhB*MJn17q*rJi%S;bv%VgRzl#dE#pfG9ujkPXYdzZr! zplGEnQL(&;5JkEQM^iNZbw-1sQ=jEHRJ@l-?s-A|W3$RvMcWLTLest{%xP2xWFfgv z_oPPX9t|3xg_JXU!N_bH%MCcBd#lAVUsB#u6uAL_3WM}+i|$?;0XuyrV;hU_RsQPH z8f?eX{z1!o_*b#9BRZ;b8z5MS;;Vj%fty7U1`YM9lKMC1mJmVjo3t*YOkgjLp2OT> zr9?@A(vb>T&v9Bseut$M570t*=^2T{Rc&F(TL-}+ZQavi%b1nFC!O{LIhew??;?cG z1>tPhhkRwJaCA`PBY!nU-99#0*=}p2cMj;u85Ja+oUvdV^SIj^jo1=*WdsYn8qq5D z5KD4dk&ee!gm5=dv1tBBj!?bX#sql0Oq}GP=LnqXMq=`AV+p;LK9-(5%&C!#D$tco zL_;MBZ3V6$386{;rc;=KHm9pq5Uo!H_9|LilYd&Y$j%K>NtlZQ?JkC)@9C*fv z0*j`$#i}$Xoy|!6@t@4v)r9-GtX(h78DQ3~?=fr4h;F&t-9)x~nce&`8+**-t!xi*>7_*VRBEhvG@iJkGe(bEh$hd`ZuBIA0S`F6OoE5iIq|sdMaWRC zwg`Pl4f+rir(-+1i%#8>QcZp}*zT`jS{*eW4O`?O)_9V`_+t`45qP-3aglEHri>cx zcHpt7A4|AI-&jZc6o=o<8kI+@Ym6AL1+)O0UvyGDc08im5L)wfy3uK2D_W!H)E#w= zXH(d0@r6II?)K{-vM#0FiU$?wU`cGp6i1s7n;aG*mk=hwdKis0!(b1uzfiR0kH9=7)N0aDwA-Y3}?x;&? zgXkVym>d%-8VIxK7%n=7MEA>$v7w@YFpG}mqGL&PbAQ}8DpWKOX3?X#=usrPUv9LA ziUz_g+RjDWNp!#5I4)E)5N6Tixae^tx?gUL3l$B7S#%s19Y>=3<;IDjqJc1rp2$T{ zB+>nHV?wBCAk3l@xab5D-7hyz4HXT9S@cvcdMb(Tmm6n% zrY>?FC&flfAi^Zy*mw8Gr6S!#DK$m{ktXrR{<~MV@&PS4NXkqnNSUz`h&Bl~_TWax z{;~Cw0ySFlAuTvm>caA=QsyWL#F&H|`*5RUxo|OF@W{-y|tq)tyl$;l_U4=u$3RjG2633l5k%&#xwBt9oNM$u@T6Mt^eI zV$kg7Pm$d)DO}Z>aVFu$uDtL56xo%HGg;M|IFoQ=XWn;ziu|g^scOwclW=2q-gkeB z{Hm6zYE6PkxUoa;yFW#KRl8KRW~xcJu}kl}Lq&dNyHvGihDo-uPw%@wMRrxQRJA6_ zB;44m_uZfU?b7@1%mMW0eRt*nI`cj|a{xVg-<>H|9jZ;r8k5|6Pu_QD4xlIRyFUle zllR@91L(0-GRN3Vv^EX$tgqX`U)xl?7I?v24K zy$07#8aKbW$TW_U8tU9VDIG>Ns(TmWCrUlzOnPMZ&h!|(Ec6(LRMJD^wkM_8s7Lkm zLQjI!6KB#RdwQnF;BcYGFuIZ+8p83?icyd1^o5?OQqM$_9@*(LJqF(kJ%)jn^w4PT zNoh0cQT@NrGehc0FzJ!~KhtBV211Wv&?P-RR0A~>2t7$s&s38hITSEG1E~jURN#8p zn@DU@Z&D*i1*T>o)j$mpB0FI+J4q%za)4lZ22u^wSRwR82tArn4R6UBQ!Hsf}WYq)2NHvirHKKEkWNHRd z2h_MCvJ)%yM4R*obE27^fz$yt#0Wj3q@Ea)9^TO=$1ptusRnAa5qj)WPpnCg=xJk_ zo`F;YQ6Q=Z7$^0NGU*XL?I@;a0M%e1BhNsp!9YfyfmDNmj64IW1_K#+ezqDU`V7lz zHQunjW^A^|^;qxg4WsWbyzCFtOw$1pE_a}HHOL6zeOPZ8JfnXps@C+rbJTZ#s$N|PSF6bpM&(3AJwp9AR0`|i;G_vC%|=Ky;0Ui^t6p~e%9)i8S(qQ*__r$(=+MfZ}9 za9@~bD3-yNw-fTEbo=KA_H%43 zsUXmk%G#DYHAcO3m27&ShTfv#53~k+%$q9uFG}axlu_8JIIRfpj zC4vr$eyOiy1jojT-{`rS)~PSl+hu=J&5GWR0rWRK<% zdQ&M=_^pc%zx$=qrQ*N~I_w8JhimEe08qWD)VT3-5PRR}KA>C200CaS%)?a*+Rc;% z@3bT{az*VNdZ(rM1H3ZP4%J#~gnW5IByZFwusJ&36M!UY+_0%g*o4=aY9qyK23J6^ zddC*?t+&(v+TXj{X zg|~}eJq<1+UpDQ=&qVM`+iB0^2W@zf#ec(V$Rw4bqC6ld3qT<}d^Wxlsb<&WU+F>J zn?kN^+D#uM5JsN~R%zlC{LUJ5zwVuCRlghqzx#kErn;MzRfB|ir7)Dy+)Xg6bbN`CI?CgIIzfPRxc|G>Tt5&RScFq)Qd zc+jFZ<8GSK=s08V(1S@khG=1r&1p$ETsGv*xaVgy9z0{yDUpn^mw%T5k2h}AH3#vj zxToR9@1L|waL>SfDQ^6{PMd{03HNN=Dp3yr$n3i%^x+BmDL(x|kv=LxACjPtN05K$ zBN6nzA$<&jjHHh~(1#xA)i8R)j6N|$-}X2SH@#AGI&S(L(Ma6X=IJ{jJ*Z|V`Zmhh zxJThW2lr^)^nDLI?sIXUhkGpUak$6hJ|Fi5xZ`k7zn4nB4Bk=6_Tr*ufg#Iw#WxNiHhSs(p%!qJQGSpL5D zwY}^46*s=U_tM2nu6fV*&9ncVpE9M=@uT(D)TBqAUiw(_J@@`$#t*OVI(%+);QIrA zk36l-`MU)pUw>-T?5ve%maMw%%731BVRhBQf4>y-*$t=sqiOQYp^FBMyMEsXe_s9D zZO!YxYP=-hd+>^9@~-MQmc9PDh~4Gm<3~ODLcRY}*O*`J@!VZ>*^6s*+wtKU)2@E` z&bo6R{=BnhMD8cQ+4|7vyIS5&kNek_Ke~s#_T{{sxerW=+L`vs8Ow&;crao56Ej{* ze(d4fzW?~Y8;)Lj`$HE8?wb0W`3qkEtmW;rTeePqa^Kk5|6a1~lzIQze?>(6AJ@cv zGVXuMRy?{W-}Cv)$Twb z0kGrMK;RmLTM?Ec{52YM4Zy1$iWfS$n!YhsEx+G4BHXchDR?P9=kZ| zl$#?qYje(;b>Wn8=K;%j79jirWqKtc<_?d((>j0HAnRgC;`A(_V_pda*f-B4-FCo2 z45Z@@+;77Nvq+DkPiJ(p<8j? zk3nB;qOVl+Na&|QiG4dwrnluTc-JpI;(N}RBDW7?u ze+9a3q+91J{aZkfdL6$TFw*ma>HiG69rW!+y7eKI{vFV_gMPD#UaaVCpq~K!u!(+9 z(Ib(s=idkfs9#m~=LGdn1br{)Uqgpk>z*!7F$J_?e)Q24D*u< zd{f@*-OjC`-wk?icD?}m0$(7o#b~GXE|rfDLEi~_Z{^Sd`r!9__frhoT;$Ktr-D8L z^hGB93zYse&}V{vkBNS_qPswU26XnVJXuawx1w(aeI)t>_KibHw^>ztc>(m3ptEoA zDf+Xj9)1YA7kvZ!ww|ItH<}bsJ?sG8j-M%9YqBp*>5mE1v}-|MVe<1;O8->QxBd_a zux~ah`9+LQB7eDlDS=-~;Fl8kr38K{fnQ4CmlF7YQ3CS&ukw4ZQm6dRD@|qSqHoO7 z70CnjeQ4U_pi6#tn)Wd0lHZx7Jqx;Me}pdD$Dm7oZRTCj*5PZx{N+a!cDO z0NQV%OMVZY_FL$BMex(!3thCILKmiN{90vaA*QhGVqX*mB-e2?^`?vVV(9V`{abgX$jLOPqD`>%g(av87_{WG<7O%d-ZyeKP(Eg$YwPL%dchdhl!;et)U&^6MK=NgKl=xd1f3#{(68|8>kJPG# zd|5C5DDX8Q_@{s;`RSqoUnvCb5cr!ZgSglp0K!__#Q%-JuST4%_i%K2pR{R2^w{~B$E;$nLP2>rMT-$@&zxVE$Z*i(f(!q>67e1DpoD1v|&(=^QWr&u94V_4Z1J zKTVVUw&bVp*jv!cg|_Q#;AwYSo?{{TX+O_`UQpIsiNAy4;gnT&iBh2rDH@qPg<^mW zB_#WCBgKYZS@z#%e3Ll_LpY-AWinOX8d4H_Sa^7gzDGL_#tXMGvkM9MWX*Q zl#iXg{TBh`i{3vxU)ZeWLRbLeFt{Lf|_V zb6$D=#ZKVu!Q={}y*-i>dY-~j;G=t}=f{BW>3oOpgq+ZGB7PG1&~qGOFwyAgJdU#% zUYVzWc!uwO-o#9S4?U-2fxw5Jqmd@?q34D;1wQorjzWf4W+!txT$@+8IwQ}SIPpnTZ_+;>;*6&RB*ni*#*vDXRp%`^wW#r{%W#(iQYsDq23(K`-nS}+~ud>%U%8PJJMlK?Av$B>ImKJ0Isg=8O^Ru-@ z1!XC@kO7ja!D7L(G^}Ka1WSHQb+%>Lcg*UUv{M@1(oDyuzcaN zxhW2(Km6n8PF9hpn}i%QB8~--=b0D|Opysfjzi&R zxbX8;l9T1>A#`L&w-ZU|L=@A}DC8_Uq{VOmi}55Bewd0`C|RLSR$&@~hnpDAdm*-v zGgsz!J3J-iG#Y*)Oi*3oRDL3g@rab*i7!1JIK#@j52w`xbx=8xt;PdkwD^*B`L2xB z2wlZI%#lz*p({InZ9!@L>eAf2tVy|98jIy*l;mjfS?darl83HhPN9}SRhqGhh!eEp z?7R$0AfTc=mln_b6Yt6{2h2|9i7zf>hs4BZ=ZNN)lZCW0##8Vn%Tq}B#!RqLQqlt{ z%%+U|+)UU}2lqPc?`+W8L8h1UM;T&r!7e;v!E7|RpCH%e$-ohd zN-q}Na$iC2AAp5jB*S>ox{cQIGQFIS$}m|lOZz3C3~3rqdjm2q=dChK5a~nh7icXD z0MTT6Ilq-5tyAcd`epga{1<>m>E{s>F1f!U!yH3;T6%@1F9(iTk=#F$>rt7XWDh6G~S$=;hG^rq1CVj(Hl=|3hDSMZYQCFTwQM*VW0E z-i9<&t|R2Rf}#8}F2h|&8)Zl@*BhNHfy5>Gcq|x~_ CHECKPOINT_SAVE_TIIME: + try: + self._last_check_time = current_time + check_point_tracker.save_check_point(True) + except Exception: + traceback.print_exc() + else: + try: + check_point_tracker.save_check_point(False) + except Exception: + traceback.print_exc() + return None + + def process(self, log_groups, check_point_tracker): + for log_group in log_groups.LogGroups: + for log in log_group.Logs: + d = {} + for content in log.Contents: + d[content.Key] = content.Value + self.cli.push(d) + self._saveCheckpoint(check_point_tracker) + + +class Cslscon(object): + def __init__(self, decode): + e1, e2 = decode_lib("%s" % decode) + slsConD = { + "host": "%s.log.aliyuncs.com"%region, + "project": "sysom-metrics-%s"%region, + "logstore": "offline", + "user": e1, + "pass": e2, + "group": "inner", + "number": "1" + } + self._argd = slsConD.copy() + super(Cslscon, self).__init__() + + def _setupOpt(self, group_name): + return LogHubConfig(self._argd['host'], + self._argd['user'], + self._argd['pass'], + self._argd['project'], + self._argd['logstore'], + self._argd['group'], + group_name) + + def _startOpt(self, opt): + w = ConsumerWorker(CslsHander, consumer_option=opt) + self._ws.append(w) + w.start() + + def _installOpts(self, cli, header): + self._ws = [] + CslsHander.cli = cli + if header: + CslsHander.header = header + if "gname" in self._argd: + opt = self._setupOpt(self._argd['gname']) + self._startOpt(opt) + print("%s start." % self._argd['gname']) + return + for i in range(int(self._argd['number'])): + group_name = self._argd['group'] + "_%d" % os.getpid() + "_%d" % i + opt = self._setupOpt(group_name) + self._startOpt(opt) + print("%s start.." % group_name) + + def consume(self, cli, header=None): + self._installOpts(cli, header) + + def stop(self): + for w in self._ws: + w.shutdown() + diff --git a/sysom_server/sysom_offline_push/app/query.py b/sysom_server/sysom_offline_push/app/query.py new file mode 100644 index 00000000..fcbcd089 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/query.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- # +""" +Time 2023/09/19 15:41 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File schemas.py +Description: +""" +from typing import Optional +from app import models +from sysom_utils import BaseQueryParams + + +# class PersonQueryParams(BaseQueryParams): + +# # 1. 指定要查询的模型 +# __modelclass__ = models.Person + +# # 2. 定义排序字段 +# sort: str = "-created_at" + +# # 3. 定义支持用于过滤的参数 +# name: Optional[str] = None +# age: Optional[str] = None + +# # 4. 指定哪些字段是枚举类型,并且指明对应的枚举类 +# __enum_fields__ = { +# } \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/app/routers/health.py b/sysom_server/sysom_offline_push/app/routers/health.py new file mode 100644 index 00000000..cd614ff7 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/routers/health.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File health.py +Description: +""" +from fastapi import APIRouter + + +router = APIRouter() + + +@router.get("/check") +async def get_channel_config(): + return { + "code": 0, + "err_msg": "", + "data": "" + } diff --git a/sysom_server/sysom_offline_push/app/routers/person.py b/sysom_server/sysom_offline_push/app/routers/person.py new file mode 100644 index 00000000..ebf6e39c --- /dev/null +++ b/sysom_server/sysom_offline_push/app/routers/person.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File health.py +Description: +""" +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from app.query import PersonQueryParams +from app.database import get_db +from app.crud import get_person_list, get_person_by_name +from app.schemas import Person +from sysom_utils import StandardListResponse, StandardResponse + + +router = APIRouter() + + +@router.get("/get") +async def get_specific_person( + person_name: str, db: Session = Depends(get_db) +): + person = get_person_by_name(db, person_name) + return StandardResponse(person, Person) + +@router.get("/list") +async def get_persons( + query_params: PersonQueryParams = Depends(), db: Session = Depends(get_db) +): + person_list = get_person_list(db, query_params) + return StandardListResponse(person_list, Person) diff --git a/sysom_server/sysom_offline_push/app/schemas.py b/sysom_server/sysom_offline_push/app/schemas.py new file mode 100644 index 00000000..4e8bdef4 --- /dev/null +++ b/sysom_server/sysom_offline_push/app/schemas.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File schemas.py +Description: +""" +from pydantic import BaseModel +from datetime import datetime + +########################################################################### +# Define schemas here +########################################################################### + +# @reference https://fastapi.tiangolo.com/zh/tutorial/response-model/ +# class Person(BaseModel): +# id: int +# name: str +# age: int +# created_at: datetime + +# class Config: +# orm_mode = True \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/conf/common.py b/sysom_server/sysom_offline_push/conf/common.py new file mode 100644 index 00000000..a176ddb0 --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/common.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File common.py +Description: +""" +from pathlib import Path +from sysom_utils import ConfigParser, SysomFramework + +BASE_DIR = Path(__file__).resolve().parent.parent + +################################################################## +# Load yaml config first +################################################################## +YAML_GLOBAL_CONFIG_PATH = f"{BASE_DIR.parent.parent}/conf/config.yml" +YAML_SERVICE_CONFIG_PATH = f"{BASE_DIR}/config.yml" + +YAML_CONFIG = ConfigParser(YAML_GLOBAL_CONFIG_PATH, YAML_SERVICE_CONFIG_PATH) + +mysql_config = YAML_CONFIG.get_server_config().db.mysql +service_config = YAML_CONFIG.get_service_config() + +SysomFramework.init(YAML_CONFIG) + +################################################################## +# fastapi config +################################################################## +SQLALCHEMY_DATABASE_URL = ( + f"{mysql_config.dialect}+{mysql_config.engine}://{mysql_config.user}:{mysql_config.password}@" + f"{mysql_config.host}:{mysql_config.port}/{mysql_config.database}" +) \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/conf/develop.py b/sysom_server/sysom_offline_push/conf/develop.py new file mode 100644 index 00000000..af922d74 --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/develop.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File develoop.py +Description: +""" +from .common import * + +''' +开发环境配置项 +''' + +DEBUG = True \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/conf/gunicorn.py b/sysom_server/sysom_offline_push/conf/gunicorn.py new file mode 100644 index 00000000..b769d258 --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/gunicorn.py @@ -0,0 +1,23 @@ +''' +Channel Service Gunicorn Settings +''' +from conf.common import YAML_CONFIG + +bind = YAML_CONFIG.get_service_config().get("bind", "127.0.0.1") +port = YAML_CONFIG.get_service_config().get("port", "80") + +workers = 2 # 指定工作进程数 + +threads = 3 + +bind = f'{bind}:{port}' + +worker_class = 'uvicorn.workers.UvicornWorker' # 工作模式线程, 默认为sync模式 + +max_requests = 2000 # 设置最大并发数量为2000 (每个worker处理请求的工作线程) + +accesslog = '/var/log/sysom/sysom-offline_push-access.log' + +loglevel = 'error' + +proc_name = 'sysom_offline_push_service' diff --git a/sysom_server/sysom_offline_push/conf/product.py b/sysom_server/sysom_offline_push/conf/product.py new file mode 100644 index 00000000..efce0452 --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/product.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File product.py +Description: +""" +from .common import * + +''' +生产环境配置项 +''' + +DEBUG = False diff --git a/sysom_server/sysom_offline_push/conf/settings.py b/sysom_server/sysom_offline_push/conf/settings.py new file mode 100644 index 00000000..40550d5e --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/settings.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File settings.py +Description: +""" +import os + +env = os.environ.get("env", "product") + + +if env == "develop": + from .develop import * +elif env == "testing": + from .testing import * +elif env == "product": + from .product import * \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/conf/testing.py b/sysom_server/sysom_offline_push/conf/testing.py new file mode 100644 index 00000000..4bfc178d --- /dev/null +++ b/sysom_server/sysom_offline_push/conf/testing.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- # +""" +Time 2024/01/19 14:22 +Author: mingfeng (SunnyQjm) +Email mfeng@linux.alibaba.com +File testing.py +Description: +""" +from .common import * + +''' +测试环境配置项 +''' +DEBUG = True diff --git a/sysom_server/sysom_offline_push/config.yml b/sysom_server/sysom_offline_push/config.yml new file mode 100644 index 00000000..46e21269 --- /dev/null +++ b/sysom_server/sysom_offline_push/config.yml @@ -0,0 +1,37 @@ +vars: + SERVICE_NAME: &SERVICE_NAME sysom_offline_push + SERVICE_CONSUMER_GROUP: + !concat &SERVICE_CONSUMER_GROUP [*SERVICE_NAME, "_consumer_group"] + +sysom_server: + cec: + consumer_group: *SERVICE_CONSUMER_GROUP + +sysom_service: + service_name: *SERVICE_NAME + service_dir: *SERVICE_NAME + protocol: http + host: 127.0.0.1 + bind: 127.0.0.1 + port: 7021 + SYSOM_REGION: cn-zhangjiakou + SYSOM_ADDITION: McuLmtVHGGXMjeffE2ABOYq32JWg2SNyMlTwMggMSWy2cjewzAZ8XK4hRxnsItzHQuruOsqO/qtnvN4dpSuDhvPLJ+hYS7AhxFgVXV543q4= + framework: + gcache: + protocol: redis + node_dispatch: + cmg: + tags: + - offline_push + - FastApi + # Metadata of service + metadata: + check: + type: http + url: "/api/v1/offline_push/health/check" + interval: 10 + timeout: 10 + deregister: 25 + header: + tls_skip_verify: false + diff --git a/sysom_server/sysom_offline_push/lib/README.md b/sysom_server/sysom_offline_push/lib/README.md new file mode 100644 index 00000000..3ec74424 --- /dev/null +++ b/sysom_server/sysom_offline_push/lib/README.md @@ -0,0 +1 @@ +The current directory holds the public libraries or utils needed for microservices \ No newline at end of file diff --git a/sysom_server/sysom_offline_push/libaddenv.so b/sysom_server/sysom_offline_push/libaddenv.so new file mode 100644 index 0000000000000000000000000000000000000000..dac2fabf19b8762eb4acd35f79eb970414f54927 GIT binary patch literal 29976 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