From 6846a4c98a793e74ae17b47f04a0ff8b210aeb24 Mon Sep 17 00:00:00 2001
From: zhaoqingang <zhaoqg0118@163.com>
Date: 星期二, 01 四月 2025 16:52:51 +0800
Subject: [PATCH] 授权license

---
 app/service/pom/public_key.pem    |    9 +
 app/api/auth.py                   |    8 +
 app/init_config/init_run_data.py  |    3 
 /dev/null                         |   45 ---------
 app/api/system.py                 |   15 ++
 app/service/system.py             |   51 +++++++++-
 app/config/const.py               |    3 
 app/api/v2/public_api.py          |    2 
 app/models/system.py              |   14 ++
 app/utils/common.py               |   25 +++++
 main.py                           |    2 
 app/task/sync_system.py           |   68 +++++++++++++
 app/api/__init__.py               |    8 +
 app/service/v2/initialize_data.py |   24 ++++
 app/config/env_conf/system.yaml   |    1 
 15 files changed, 218 insertions(+), 60 deletions(-)

diff --git a/app/api/__init__.py b/app/api/__init__.py
index 6cb4b05..de2282f 100644
--- a/app/api/__init__.py
+++ b/app/api/__init__.py
@@ -68,6 +68,14 @@
 def get_current_user(token: str = Depends(oauth2_scheme)):
     try:
         payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        expired_time = payload.get("lex")
+        if not expired_time:
+            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,  detail="浠ょ墝鏃犳晥鎴栧凡杩囨湡",
+            headers={"WWW-Authenticate": "Bearer"})
+        if datetime.strptime(expired_time, "%Y-%m-%d %H:%M:%S") < datetime.now():
+            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,  detail="绯荤粺鎺堟潈宸茶繃鏈燂紒",
+            headers={"WWW-Authenticate": "Bearer"})
+
         username: str = payload.get("sub")
         if username is None:
             raise HTTPException(
diff --git a/app/api/auth.py b/app/api/auth.py
index a8d381d..7cbfdcf 100644
--- a/app/api/auth.py
+++ b/app/api/auth.py
@@ -6,7 +6,8 @@
 from sqlalchemy.ext.asyncio import AsyncSession
 from app.api import Response, pwd_context, get_current_user
 from app.config.config import settings
-from app.config.const import chat_server, RAGFLOW, workflow_server, DIFY, TMP_DICT
+from app.config.const import chat_server, RAGFLOW, workflow_server, DIFY, TMP_DICT, SYSTEM_ID, SYSTEM_STATUS_ON
+from app.models import SystemDataModel
 from app.models.app_token_model import AppToken
 from app.models.base_model import get_db
 from app.models.postgresql_base_model import get_pdb
@@ -130,8 +131,11 @@
         except Exception as e:
             return Response(code=500, msg=f"Failed to login with {app['id']}: {str(e)}")
     """
+    system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first()
+    if not system or system.status != SYSTEM_STATUS_ON:
+        return Response(code=400, msg="绯荤粺鐘舵�佸紓甯革紝璇锋巿鏉冩縺娲诲悗鎿嶄綔锛�")
     # 鍒涘缓鏈湴token
-    access_token = create_access_token(data={"sub": user.username, "user_id": user.id})
+    access_token = create_access_token(data={"sub": user.username, "user_id": user.id, "lex": system.expired_at.strftime('%Y-%m-%d %H:%M:%S')})
 
     # await update_token(db, user.id, access_token, token_dict)
     # await update_user_token(db, user.id, token_dict)
diff --git a/app/api/system.py b/app/api/system.py
index 72c9cec..b1d40f8 100644
--- a/app/api/system.py
+++ b/app/api/system.py
@@ -4,9 +4,10 @@
 from app.api import Response, get_current_user
 from app.models.base_model import get_db
 from app.models.role_model import RoleData, RoleModel
-from app.models.system import SystemData
+from app.models.system import SystemData, SystemLicense
 from app.models.user_model import UserModel
-from app.service.system import services_get_system_data, services_update_system_data, service_upload_logo_image
+from app.service.system import services_get_system_data, services_update_system_data, service_upload_logo_image, \
+    services_update_system_license
 
 system_router = APIRouter()
 
@@ -33,3 +34,13 @@
     if not file_name:
         return Response(code=500, msg="failed", data={"logo": ""})
     return Response(code=200, msg="successfully", data={"logo": file_name})
+
+
+@system_router.put("/license", response_model=Response)
+async def api_update_system_license(system: SystemLicense, db=Depends(get_db)):
+
+    msg = await services_update_system_license(db, system.licenseCode)
+    if msg:
+        return Response(code=400, msg=msg, data={})
+    return Response(code=200, msg="successfully", data={})
+
diff --git a/app/api/v2/public_api.py b/app/api/v2/public_api.py
index c86b787..2f49e37 100644
--- a/app/api/v2/public_api.py
+++ b/app/api/v2/public_api.py
@@ -13,7 +13,7 @@
 from app.models.public_api_model import DfToken
 from app.service.v2.api_token import DfTokenDao
 from app.service.v2.initialize_data import dialog_menu_sync, create_menu_sync, user_update_app
-from app.task.sync_resources import sync_knowledge, sync_dialog, sync_agent, sync_llm, sync_resource
+# from app.task.sync_resources import sync_knowledge, sync_dialog, sync_agent, sync_llm, sync_resource
 from fastapi import Depends, APIRouter, File, UploadFile
 from sqlalchemy.orm import Session
 from app.config.const import smart_message_error, http_400, http_500, http_200, complex_dialog_chat
diff --git a/app/config/const.py b/app/config/const.py
index 30ee790..b0d8a39 100644
--- a/app/config/const.py
+++ b/app/config/const.py
@@ -114,6 +114,9 @@
 
 ###-------------------------------system-------------------------------------------------
 SYSTEM_ID = 1
+SYSTEM_STATUS_EXPIRED = 2
+SYSTEM_STATUS_ON = 1
+SYSTEM_STATUS_OFF = 0
 
 ### --------------------------------complex mode----------------------------------------------
 complex_dialog_chat = 1 # 鏂囨。鍜屽熀纭�瀵硅瘽
diff --git a/app/config/env_conf/system.yaml b/app/config/env_conf/system.yaml
index 4b2d6da..9cfb9f1 100644
--- a/app/config/env_conf/system.yaml
+++ b/app/config/env_conf/system.yaml
@@ -1,3 +1,4 @@
 smart_system:
   title: SmartAI澶фā鍨嬪钩鍙�
   desc: SmartAI澶фā鍨嬪钩鍙�
+  version: 1.0.3
diff --git a/app/init_config/init_run_data.py b/app/init_config/init_run_data.py
index c0ff67e..e735889 100644
--- a/app/init_config/init_run_data.py
+++ b/app/init_config/init_run_data.py
@@ -1,6 +1,6 @@
 from app.models.base_model import SessionLocal
 from app.service.v2.initialize_data import dialog_menu_sync, default_group_sync, default_role_sync, \
-    basic_agent_sync, admin_account_sync, sync_rg_api_token, sync_complex_api_token
+    basic_agent_sync, admin_account_sync, sync_rg_api_token, sync_complex_api_token, system_license_sync
 from app.task.sync_account_token import sync_token
 
 
@@ -15,6 +15,7 @@
         await sync_rg_api_token(db)  # rg token
         await sync_token()  # 璐﹀彿token鐧诲綍
         await sync_complex_api_token(db)  # 璐﹀彿token鐧诲綍
+        await system_license_sync(db) # 鍚屾绯荤粺license
 
     except Exception as e:
         print(e)
diff --git a/app/models/system.py b/app/models/system.py
index 28dca04..2846bf9 100644
--- a/app/models/system.py
+++ b/app/models/system.py
@@ -12,6 +12,11 @@
     id = Column(Integer, primary_key=True, index=True)
     title = Column(String(255))
     desc = Column(String(1000))
+    version = Column(String(32))
+    machine_id = Column(String(255))
+    license_code = Column(String(1000))
+    status = Column(Integer, default=0)
+    expired_at = Column(DateTime)
     created_at = Column(DateTime, default=datetime.now())
     updated_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now())
 
@@ -21,6 +26,11 @@
             # 'id': self.id,
             'title': self.title,
             'desc': self.desc,
+            'version': self.version,
+            'machine_id': self.machine_id,
+            'license_code': self.license_code,
+            'status': self.status,
+            'expired_at': self.expired_at.strftime('%Y-%m-%d %H:%M') if self.expired_at else '',
         }
 
     def __repr__(self):
@@ -32,3 +42,7 @@
     title: str
     desc: str
     logo: str
+
+class SystemLicense(BaseModel):
+    licenseCode: str
+
diff --git a/app/service/pom/public_key.pem b/app/service/pom/public_key.pem
new file mode 100644
index 0000000..746caa3
--- /dev/null
+++ b/app/service/pom/public_key.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8Jak4kGIkOE2kyI0oTcb
+w3yk7OfZ78g1RGvxKlYbKWz93Prxi1pvywXHOnrL/IYDCaNFOybFy5aMbqqvqXOx
+0LBCqwmB9F07AiEysmhH5m5OxlS9XsxGZb1WeRmobRbge3Hxl59DmUKvD/7Gdsre
+JnDeSWxeaS/zIqLVUsvV3301B08biIywMAKamBQyuJNTPK1ir8iy6peSLPi022zk
+Nl+Rm4ToOrF00oqwB8z5BOTdDcJW/eFlieOyTnWSAFBTIXAB9uqZSjn37kyLKYDh
+yVqB71T/wQvMRip4PPFpCE4UCGGhLHHsKPhtCgxHj6YqE7vUCuGBXP/aagzpWC/H
+ywIDAQAB
+-----END PUBLIC KEY-----
diff --git a/app/service/system.py b/app/service/system.py
index 4df1e03..086fbae 100644
--- a/app/service/system.py
+++ b/app/service/system.py
@@ -1,15 +1,20 @@
 import os
 import shutil
 import uuid
-from datetime import datetime
-
 import yaml
-from fastapi import UploadFile
 
+from datetime import datetime
+from fastapi import UploadFile
 from Log import logger
-from app.api import pwd_context
-from app.config.const import SYSTEM_ID, ENV_CONF_PATH, APP_STATIC_PATH
+from app.config.const import SYSTEM_ID, ENV_CONF_PATH, APP_STATIC_PATH, APP_SERVICE_PATH, SYSTEM_STATUS_ON
 from app.models.system import SystemDataModel
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes
+import base64
+import json
+
+from app.utils.common import get_machine_id
 
 
 async def services_get_system_data(db):
@@ -18,7 +23,7 @@
         with open(os.path.join(ENV_CONF_PATH, "system.yaml"), 'r', encoding='utf-8') as file:
             # 鍔犺浇JSON鏁版嵁
             config = yaml.safe_load(file)
-            system = SystemDataModel(id=SYSTEM_ID, title=config["smart_system"]["title"], desc=config["smart_system"]["desc"])
+            system = SystemDataModel(id=SYSTEM_ID, title=config["smart_system"]["title"], desc=config["smart_system"]["desc"], version=config["smart_system"]["version"])
             db.add(system)
             db.commit()
             db.refresh(system)
@@ -53,3 +58,37 @@
     except Exception as e:
         logger.error(f"淇濆瓨澶辫触: {str(e)}")
         return ""
+
+
+async def services_update_system_license(db, license_code):
+    try:
+        with open(os.path.join(APP_SERVICE_PATH, "pom/public_key.pem"), "rb") as f:
+            public_key = serialization.load_pem_public_key(f.read())
+        license_data, signature = base64.b64decode(license_code).split(b"-----", 1)
+        # print(license_data)
+        public_key.verify(
+            signature,
+            license_data,
+            padding.PSS(
+                mgf=padding.MGF1(hashes.SHA256()),
+                salt_length=padding.PSS.MAX_LENGTH
+            ),
+            hashes.SHA256()
+        )
+        license_dict = json.loads(license_data.decode('utf-8'))
+        # print(license_dict)
+        expiration_date = datetime.fromisoformat(license_dict['expiration_date'])
+        if expiration_date < datetime.now():
+            return "鎺堟潈鐮佸凡杩囨湡"
+        system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first()
+        if license_dict['machine_id'] != get_machine_id() or system.machine_id != license_dict['machine_id']:
+            return "鎺堟潈鐮佹棤鏁�"
+        system.license_code = license_code
+        system.expired_at = expiration_date
+        system.status = SYSTEM_STATUS_ON
+        system.updated_at = datetime.now()
+        # db.query(SystemDataModel).filter_by(id=SYSTEM_ID).update({"license_code": license_code, "expired_at": expiration_date, "status": 1, "updated_at": datetime.now()})
+        db.commit()
+        return ""
+    except Exception as e:
+        return f"楠岃瘉澶辫触: {str(e)}"
diff --git a/app/service/v2/initialize_data.py b/app/service/v2/initialize_data.py
index 1cfad66..28a9961 100644
--- a/app/service/v2/initialize_data.py
+++ b/app/service/v2/initialize_data.py
@@ -10,9 +10,9 @@
 from app.config.agent_base_url import RG_APP_TOKEN_LIST, RG_APP_NEW_TOKEN, DF_CHAT_API_KEY
 # from app.api import pwd_context
 from app.config.const import DIFY, ENV_CONF_PATH, RAGFLOW, smart_server, chat_server, workflow_server, TMP_DICT, \
-    rg_api_token, Dialog_STATSU_ON
+    rg_api_token, Dialog_STATSU_ON, SYSTEM_ID
 from app.models import MenuCapacityModel, WebMenuModel, GroupModel, RoleModel, DialogModel, UserModel, UserAppModel, \
-    cipher_suite, UserTokenModel, ApiTokenModel, ComplexChatModel
+    cipher_suite, UserTokenModel, ApiTokenModel, ComplexChatModel, SystemDataModel
 from app.service.auth import UserAppDao
 from app.service.bisheng import BishengService
 from app.service.difyService import DifyService
@@ -22,6 +22,7 @@
 from app.service.v2.app_register import AppRegisterDao
 from app.config.config import settings
 from app.service.v2.chat import get_app_token
+from app.utils.common import get_machine_id
 from app.utils.password_handle import generate_password, password_encrypted, password_decrypted
 
 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
@@ -427,4 +428,21 @@
 
     except Exception as e:
         print(e)
-        db.rollback()
\ No newline at end of file
+        db.rollback()
+
+
+async def system_license_sync(db):
+    with open(os.path.join(ENV_CONF_PATH, "system.yaml") , 'r', encoding='utf-8') as file:
+        # 鍔犺浇JSON鏁版嵁
+        config = json.load(file)
+        try:
+            system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first()
+            if system:
+                system.version = config["smart_system"].get("version")
+            else:
+                system = SystemDataModel(id=SYSTEM_ID, version=config["smart_system"].get("version"), title=config["smart_system"].get("title"), desc=config["smart_system"].get("desc"), machine_id=get_machine_id())
+                db.add(system)
+            db.commit()
+        except Exception as e:
+            print(e)
+            db.rollback()
diff --git a/app/task/sync_resources.py b/app/task/sync_resources.py
deleted file mode 100644
index fca2ae8..0000000
--- a/app/task/sync_resources.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import asyncio
-
-from fastapi import Depends
-
-from Log import logger
-from app.config.config import settings
-from app.models import AgentType
-from app.models.public_api_model import AppRegisterModel
-from app.models.base_model import get_db
-from app.service.bisheng import BishengService
-from app.service.ragflow import RagflowService
-
-
-def sync_resource():
-    asyncio.run(sync_knowledge())
-    asyncio.run(sync_dialog())
-    asyncio.run(sync_agent())
-    asyncio.run(sync_llm())
-
-
-async def sync_knowledge(db=Depends(get_db)):
-
-    token = ""
-
-    register_app = db.query(AppRegisterModel).filter(AppRegisterModel.status.__eq__(1)).all()
-    for rapp in register_app:
-        if rapp.app_type == AgentType.RAGFLOW:
-            token = ""
-            ragflow_service = RagflowService(settings.fwr_base_url)
-            ragflow_service.get_knowledge_list()
-        elif rapp.app_type ==AgentType.BISHENG:
-            token = ""
-            bisheng_service = BishengService(settings.sgb_base_url)
-        else:
-            logger.error("娉ㄥ唽鏈煡搴旂敤锛歿}".format(rapp.app_type))
-
-
-async def sync_dialog():
-    ...
-
-async def sync_agent():
-    ...
-
-async def sync_llm():
-    ...
\ No newline at end of file
diff --git a/app/task/sync_system.py b/app/task/sync_system.py
new file mode 100644
index 0000000..4d3c3d0
--- /dev/null
+++ b/app/task/sync_system.py
@@ -0,0 +1,68 @@
+import asyncio
+import base64
+import json
+import os
+
+from datetime import datetime
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes
+from app.config.const import SYSTEM_ID, SYSTEM_STATUS_EXPIRED, SYSTEM_STATUS_ON, APP_SERVICE_PATH
+from app.models import SystemDataModel
+from app.models.base_model import SessionLocal
+from app.utils.common import get_machine_id
+
+
+async def sync_system_license():
+    print("-------------------------------------------")
+    db = SessionLocal()
+    try:
+        system = db.query(SystemDataModel).filter_by(id=SYSTEM_ID).first()
+        if not system:
+            return
+        machine_id = get_machine_id()
+        if system.machine_id:
+            if system.machine_id != machine_id:
+                system.machine_id = machine_id
+                if system.status == SYSTEM_STATUS_ON:
+                    system.status = SYSTEM_STATUS_EXPIRED
+            else:
+                if system.status == SYSTEM_STATUS_ON:
+                    if not system.license_code or system.expired_at < datetime.now():
+                        system.status = SYSTEM_STATUS_EXPIRED
+                    else:
+                        try:
+                            with open(os.path.join(APP_SERVICE_PATH, "pom/public_key.pem"), "rb") as f:
+                                public_key = serialization.load_pem_public_key(f.read())
+                            license_data, signature = base64.b64decode(system.license_code).split(b"-----", 1)
+                            # print(license_data)
+                            public_key.verify(
+                                signature,
+                                license_data,
+                                padding.PSS(
+                                    mgf=padding.MGF1(hashes.SHA256()),
+                                    salt_length=padding.PSS.MAX_LENGTH
+                                ),
+                                hashes.SHA256()
+                            )
+                            license_dict = json.loads(license_data.decode('utf-8'))
+                            # print(license_dict)
+                            expiration_date = datetime.fromisoformat(license_dict['expiration_date'])
+                            system.expired_at = expiration_date
+                            if expiration_date < datetime.now():
+                                system.status = SYSTEM_STATUS_EXPIRED
+                        except Exception as e:
+                            print(e)
+                            system.status = SYSTEM_STATUS_EXPIRED
+        else:
+            if system.status == SYSTEM_STATUS_ON:
+                system.status = SYSTEM_STATUS_EXPIRED
+        db.commit()
+    except Exception as e:
+        print(e)
+    finally:
+        db.close()
+
+
+def sync_resource():
+    asyncio.run(sync_system_license())
diff --git a/app/utils/common.py b/app/utils/common.py
index e3ac4a6..cacda03 100644
--- a/app/utils/common.py
+++ b/app/utils/common.py
@@ -1,6 +1,31 @@
 import pytz
+import platform
+import subprocess
+
 from datetime import datetime
+
 
 def current_time():
     tz = pytz.timezone('Asia/Shanghai')
     return datetime.now(tz)
+
+
+def get_machine_id():
+    """鑾峰彇鏈哄櫒鐨勫敮涓�鏍囪瘑"""
+    if platform.system() == "Windows":
+        # Windows 绯荤粺
+        command = "wmic csproduct get UUID"
+        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
+        output, _ = process.communicate()
+        machine_id = output.decode().strip().split("\n")[1].strip()
+    elif platform.system() == "Darwin":
+        # macOS 绯荤粺
+        command = "system_profiler SPHardwareDataType | grep 'Hardware UUID' | awk '{print $4}'"
+        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+        output, _ = process.communicate()
+        machine_id = output.decode().strip()
+    else:
+        # Linux 绯荤粺
+        machine_id = open("/etc/machine-id").read().strip()
+    # print(machine_id)
+    return machine_id
\ No newline at end of file
diff --git a/main.py b/main.py
index 3ec7a9d..1743d2c 100644
--- a/main.py
+++ b/main.py
@@ -27,6 +27,7 @@
     sync_resources_from_json
 from app.init_config.init_run_data import sync_default_data
 from app.task.sync_account_token import start_sync_token_task
+from app.task.sync_system import sync_resource
 
 init_db()
 
@@ -56,6 +57,7 @@
 scheduler = BackgroundScheduler()
 scheduler.add_job(sync_agents_v2, 'interval', minutes=60, id="sync_resource_data")
 scheduler.add_job(start_sync_token_task, 'interval', minutes=5, id="sync_token_1")
+scheduler.add_job(sync_resource, 'interval', minutes=5, id="sync_resource_1")
 
 scheduler.start()
 

--
Gitblit v1.8.0