diff --git a/pdm.lock b/pdm.lock index 7a464ca2..8bd96cd1 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default"] strategy = ["cross_platform"] lock_version = "4.4" -content_hash = "sha256:c6d5001ad8007f670b048d2f4d38cf3bdcfc2b5188d7e7bb794c36e214176bcf" +content_hash = "sha256:d203557781e5ca17f80e94d60cc6fb8e435d752317eff4e3df384b5915c598fe" [[package]] name = "anyio" @@ -132,6 +132,68 @@ files = [ {file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"}, ] +[[package]] +name = "flask-cors" +version = "4.0.0" +summary = "A Flask extension adding a decorator for CORS support" +dependencies = [ + "Flask>=0.9", +] +files = [ + {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, + {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, +] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +requires_python = ">=3.8" +summary = "Add SQLAlchemy support to your Flask application." +dependencies = [ + "flask>=2.2.5", + "sqlalchemy>=2.0.16", +] +files = [ + {file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"}, + {file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"}, +] + +[[package]] +name = "greenlet" +version = "3.0.3" +requires_python = ">=3.7" +summary = "Lightweight in-process concurrent programming" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + [[package]] name = "h11" version = "0.14.0" @@ -294,6 +356,44 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.23" +requires_python = ">=3.7" +summary = "Database Abstraction Library" +dependencies = [ + "greenlet!=0.4.17; platform_machine == \"aarch64\" or (platform_machine == \"ppc64le\" or (platform_machine == \"x86_64\" or (platform_machine == \"amd64\" or (platform_machine == \"AMD64\" or (platform_machine == \"win32\" or platform_machine == \"WIN32\")))))", + "typing-extensions>=4.2.0", +] +files = [ + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:638c2c0b6b4661a4fd264f6fb804eccd392745c5887f9317feb64bb7cb03b3ea"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3b5036aa326dc2df50cba3c958e29b291a80f604b1afa4c8ce73e78e1c9f01d"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:787af80107fb691934a01889ca8f82a44adedbf5ef3d6ad7d0f0b9ac557e0c34"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14eba45983d2f48f7546bb32b47937ee2cafae353646295f0e99f35b14286ab"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0666031df46b9badba9bed00092a1ffa3aa063a5e68fa244acd9f08070e936d3"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89a01238fcb9a8af118eaad3ffcc5dedaacbd429dc6fdc43fe430d3a941ff965"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win32.whl", hash = "sha256:cabafc7837b6cec61c0e1e5c6d14ef250b675fa9c3060ed8a7e38653bd732ff8"}, + {file = "SQLAlchemy-2.0.23-cp310-cp310-win_amd64.whl", hash = "sha256:87a3d6b53c39cd173990de2f5f4b83431d534a74f0e2f88bd16eabb5667e65c6"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d5578e6863eeb998980c212a39106ea139bdc0b3f73291b96e27c929c90cd8e1"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62d9e964870ea5ade4bc870ac4004c456efe75fb50404c03c5fd61f8bc669a72"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c80c38bd2ea35b97cbf7c21aeb129dcbebbf344ee01a7141016ab7b851464f8e"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75eefe09e98043cff2fb8af9796e20747ae870c903dc61d41b0c2e55128f958d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd45a5b6c68357578263d74daab6ff9439517f87da63442d244f9f23df56138d"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a86cb7063e2c9fb8e774f77fbf8475516d270a3e989da55fa05d08089d77f8c4"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win32.whl", hash = "sha256:b41f5d65b54cdf4934ecede2f41b9c60c9f785620416e8e6c48349ab18643855"}, + {file = "SQLAlchemy-2.0.23-cp311-cp311-win_amd64.whl", hash = "sha256:9ca922f305d67605668e93991aaf2c12239c78207bca3b891cd51a4515c72e22"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0f7fb0c7527c41fa6fcae2be537ac137f636a41b4c5a4c58914541e2f436b45"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c424983ab447dab126c39d3ce3be5bee95700783204a72549c3dceffe0fc8f4"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f508ba8f89e0a5ecdfd3761f82dda2a3d7b678a626967608f4273e0dba8f07ac"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6463aa765cf02b9247e38b35853923edbf2f6fd1963df88706bc1d02410a5577"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e599a51acf3cc4d31d1a0cf248d8f8d863b6386d2b6782c5074427ebb7803bda"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd54601ef9cc455a0c61e5245f690c8a3ad67ddb03d3b91c361d076def0b4c60"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win32.whl", hash = "sha256:42d0b0290a8fb0165ea2c2781ae66e95cca6e27a2fbe1016ff8db3112ac1e846"}, + {file = "SQLAlchemy-2.0.23-cp312-cp312-win_amd64.whl", hash = "sha256:227135ef1e48165f37590b8bfc44ed7ff4c074bf04dc8d6f8e7f1c14a94aa6ca"}, + {file = "SQLAlchemy-2.0.23-py3-none-any.whl", hash = "sha256:31952bbc527d633b9479f5f81e8b9dfada00b91d6baba021a869095f1a97006d"}, + {file = "SQLAlchemy-2.0.23.tar.gz", hash = "sha256:c1bda93cbbe4aa2aa0aa8655c5aeda505cd219ff3e8da91d1d329e143e4aff69"}, +] + [[package]] name = "typing-extensions" version = "4.9.0" diff --git a/pyproject.toml b/pyproject.toml index 2b2909f9..3919f337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,8 @@ dependencies = [ "python-dotenv>=1.0.0", "ca-lark-oauth==0.0.5", "ca-lark-webhook>=0.0.3", + "flask-sqlalchemy>=3.1.1", + "flask-cors>=4.0.0", ] requires-python = ">=3.10" readme = "README.md" diff --git a/requirements.txt b/requirements.txt index b2e5575a..411b0b3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,10 @@ certifi==2023.11.17 click==8.1.7 colorama==0.4.6; platform_system == "Windows" exceptiongroup==1.2.0; python_version < "3.11" -flask==3.0.0 +Flask==3.0.0 +flask-cors==4.0.0 +flask-sqlalchemy==3.1.1 +greenlet==3.0.3; platform_machine == "win32" or platform_machine == "WIN32" or platform_machine == "AMD64" or platform_machine == "amd64" or platform_machine == "x86_64" or platform_machine == "ppc64le" or platform_machine == "aarch64" h11==0.14.0 httpcore==1.0.2 httpx==0.26.0 @@ -21,5 +24,6 @@ MarkupSafe==2.1.3 pycryptodome==3.19.1 python-dotenv==1.0.0 sniffio==1.3.0 -typing-extensions==4.9.0; python_version < "3.11" +sqlalchemy==2.0.23 +typing-extensions==4.9.0 Werkzeug==3.0.1 diff --git a/server/app.py b/server/app.py index 5ac3ba3d..c0ebda87 100644 --- a/server/app.py +++ b/server/app.py @@ -1,39 +1,16 @@ import os -from connectai.lark.oauth import Server as OauthServer -from connectai.lark.sdk import Bot, MarketBot -from connectai.lark.webhook import LarkServer from flask import Flask, session +from flask_cors import CORS +from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) -app.secret_key = (os.environ.get("SECRET_KEY"),) - -hook = LarkServer(prefix="/api/feishu/hook") -oauth = OauthServer(prefix="/api/feishu/oauth") - -bot = Bot( - app_id=os.environ.get("APP_ID"), - app_secret=os.environ.get("APP_SECRET"), - encrypt_key=os.environ.get("ENCRYPT_KEY"), - verification_token=os.environ.get("VERIFICATION_TOKEN"), +app.secret_key = os.environ.get("SECRET_KEY") +db = SQLAlchemy(app, engine_options={"isolation_level": "AUTOCOMMIT"}) +CORS( + app, allow_headers=["Authorization", "X-Requested-With"], supports_credentials=True ) - -@hook.on_bot_message(message_type="text", bot=bot) -def on_text_message(bot, message_id, content, *args, **kwargs): - text = content["text"] - print("reply_text", message_id, text) - bot.reply_text(message_id, "reply: " + text) - - -@oauth.on_bot_event(event_type="oauth:user_info", bot=bot) -def on_oauth_user_info(bot, event_id, user_info, *args, **kwargs): - # oauth user_info - print("oauth", user_info) - # TODO - session["user_id"] = user_info["union_id"] - return user_info - - -app.register_blueprint(oauth.get_blueprint()) -app.register_blueprint(hook.get_blueprint()) +gunicorn_logger = logging.getLogger("gunicorn.error") +app.logger.handlers = gunicorn_logger.handlers +app.logger.setLevel(gunicorn_logger.level) diff --git a/server/model/schema.py b/server/model/schema.py new file mode 100644 index 00000000..b1298f2d --- /dev/null +++ b/server/model/schema.py @@ -0,0 +1,86 @@ +import json +import logging +from datetime import datetime + +import bson +from app import db +from sqlalchemy import BINARY, String, text + + +class ObjID(BINARY): + """基于bson.ObjectId用于mysql主键的自定义类型""" + + def bind_processor(self, dialect): + def processor(value): + return ( + bson.ObjectId(value).binary if bson.ObjectId.is_valid(value) else value + ) + + return processor + + def result_processor(self, dialect, coltype): + def processor(value): + if not isinstance(value, bytes): + value = bytes(value) + return str(bson.ObjectId(value)) if bson.ObjectId.is_valid(value) else value + + return processor + + @staticmethod + def new_id(): + return str(bson.ObjectId()) + + @staticmethod + def is_valid(value): + return bson.ObjectId.is_valid(value) + + +class JSONStr(String): + """自动转换 str 和 dict 的自定义类型""" + + def bind_processor(self, dialect): + def processor(value): + try: + if isinstance(value, str) and (value[0] == "%" or value[-1] == "%"): + # 使用like筛选的情况 + return value + return json.dumps(value, ensure_ascii=False) + except Exception as e: + logging.exception(e) + return value + + return processor + + def result_processor(self, dialect, coltype): + def processor(value): + try: + return json.loads(value) + except Exception as e: + logging.exception(e) + return value + + return processor + + @staticmethod + def is_valid(value): + try: + json.loads(value) + return True + except Exception as e: + logging.exception(e) + return False + + +class User(db.Model): + __tablename__ = "user" + id = db.Column(ObjID(12), primary_key=True) + openid = db.Column(db.String(128), nullable=True, comment="外部用户ID") + name = db.Column(db.String(128), nullable=True, comment="用户名") + extra = db.Column( + JSONStr(1024), nullable=True, server_default=text("'{}'"), comment="用户其他字段" + ) + status = db.Column(db.Integer, nullable=True, default=0, server_default=text("0")) + created = db.Column(db.TIMESTAMP, nullable=False, default=datetime.utcnow) + modified = db.Column( + db.TIMESTAMP, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow + ) diff --git a/server/routes/__init__.py b/server/routes/__init__.py new file mode 100644 index 00000000..460b5644 --- /dev/null +++ b/server/routes/__init__.py @@ -0,0 +1 @@ +from .lark import * diff --git a/server/routes/lark.py b/server/routes/lark.py new file mode 100644 index 00000000..0489ddcb --- /dev/null +++ b/server/routes/lark.py @@ -0,0 +1,49 @@ +import os + +from app import app +from connectai.lark.oauth import Server as OauthServerBase +from connectai.lark.sdk import Bot, MarketBot +from connectai.lark.webhook import LarkServer as LarkServerBase + +bot = Bot( + app_id=os.environ.get("APP_ID"), + app_secret=os.environ.get("APP_SECRET"), + encrypt_key=os.environ.get("ENCRYPT_KEY"), + verification_token=os.environ.get("VERIFICATION_TOKEN"), +) + + +class LarkServer(LarkServerBase): + def get_bot(self, app_id): + # TODO search in database and create Bot() + return bot + + +class OauthServer(OauthServerBase): + def get_bot(self, app_id): + # TODO search in database and create Bot() + return bot + + +hook = LarkServer(prefix="/api/feishu/hook") +oauth = OauthServer(prefix="/api/feishu/oauth") + + +@hook.on_bot_message(message_type="text", bot=bot) +def on_text_message(bot, message_id, content, *args, **kwargs): + text = content["text"] + print("reply_text", message_id, text) + bot.reply_text(message_id, "reply: " + text) + + +@oauth.on_bot_event(event_type="oauth:user_info", bot=bot) +def on_oauth_user_info(bot, event_id, user_info, *args, **kwargs): + # oauth user_info + print("oauth", user_info) + # TODO + session["user_id"] = user_info["union_id"] + return user_info + + +app.register_blueprint(oauth.get_blueprint()) +app.register_blueprint(hook.get_blueprint()) diff --git a/server/server.py b/server/server.py index b9f1902e..36b2499e 100644 --- a/server/server.py +++ b/server/server.py @@ -1,6 +1,7 @@ import os import env +import routes from app import app if __name__ == "__main__":